NOIP2017 题解
QAQ……由于没报上名并没能亲自去,自己切一切题聊以慰藉吧……
可能等到省选的时候我就没有能力再不看题解自己切省选题了……辣鸡HZ毁我青春
D1T1 小凯的疑惑
地球人都会做,懒得写题解了……
D1T2 时间复杂度
分类讨论+递归就行了,没啥思维含量,略。
D1T3 逛公园
这题好劲啊……
看见\(k\le 50\)应该能想到这是一个\(O((n+m)k)\)的DP,由于题目要求的是比最短路长度长至多\(k\)的路径条数,因此状态定义应该是定义\(f_{i,j}\)表示从\(i\)走到终点,长度为\(i\)到终点的最短路长度+\(j\)的路径条数(如果你习惯倒序定义的话)。
转移方程也不难写出:
\]
其中\(d_i\)表示\(i\)到终点的最短路长度,边界是\(f_{n,0}=1\)。
注意到这个转移方程存在同层转移的问题:
定义最短路DAG是所有点和所有满足\(d_i=d_j+w\)的边\(<i,j>,w\)组成的图(当然由于零边的存在,这个图上可能会有零环),那么转移时所有最短路DAG上的边都会有同层转移的问题。
如果有一个可以到达(总路程不超过\(D+k\))的零环那显然答案就应该是-1了。因此我们可以对最短路DAG进行拓扑排序,最后能出来的点(也就是不能沿最短路到达零环的点)一定组成一个DAG。
所以只需要判断那些出不来(在零环上或者通向零环)的点是否可达就行了。如果答案不是-1的话就只需要转移那些能出来的点,为了解决同层转移的问题,对每层先从低层转移过来,再按照最短路DAG的拓扑序同层转移即可。这样做不需要递归,常数比记忆化搜索小很多。
(注意由于数据范围比较大,最开始求\(d_i\)和构造最短路DAG时要用Dijkstra算法,不要用SPFA。)
莫名其妙的地方都能写错,老年选手身败名裂……
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int maxn=100005,maxm=200005;
struct A{
int x,d;
A(int x,int d):x(x),d(d){}
bool operator<(const A &a)const{return d>a.d;}
};
void Dijkstra(int);
void bfs();
vector<int>G[maxn],W[maxn],G2[maxn],RG2[maxn];
bool vis[maxn];
int d[maxn],dis[maxn],du[maxn],q[maxn],u[maxm],v[maxm],w[maxm];
int T,n,m,k,p,f[maxn][55],head,tail;
int main(){
scanf("%d",&T);
while(T--){
memset(du,0,sizeof(du));
memset(f,0,sizeof(f));
scanf("%d%d%d%d",&n,&m,&k,&p);
for(int i=1;i<=n;i++){
G[i].clear();
W[i].clear();
G2[i].clear();
RG2[i].clear();
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&u[i],&v[i],&w[i]);
G[v[i]].push_back(u[i]);
W[v[i]].push_back(w[i]);
}
Dijkstra(n);
memcpy(d,dis,sizeof(d));
for(int i=1;i<=n;i++){
G[i].clear();
W[i].clear();
}
for(int i=1;i<=m;i++){
G[u[i]].push_back(v[i]);
W[u[i]].push_back(w[i]);
}
Dijkstra(1);
for(int i=1;i<=m;i++)if(d[u[i]]==d[v[i]]+w[i]){
//printf("(%d,%d) w=%d\n",u[i],v[i],w[i]);
G2[u[i]].push_back(v[i]);
RG2[v[i]].push_back(u[i]);
du[u[i]]++;
}
bfs();
bool bad=false;
for(int i=1;i<=n;i++)if(!vis[i]&&d[i]+dis[i]<=d[1]+k){
bad=true;
break;
}
if(bad){
printf("-1\n");
continue;
}
//for(int i=1;i<=n;i++)if(!vis[i])printf("d[%d]=%d dis[%d]=%d D=%d k=%d\n",i,d[i],i,dis[i],d[1],k);
f[n][0]=1;
for(int j=0;j<=k;j++){
if(j){
for(int x=1;x<=n;x++)if(vis[x])for(int i=0;i<(int)G[x].size();i++){
int t=d[x]-d[G[x][i]]+j-W[x][i];//printf("%d %d t=%d\n",x,G[x][i],t);
if(t>=0&&t<j){
f[x][j]=f[x][j]+f[G[x][i]][t];
if(f[x][j]>=p)f[x][j]-=p;
}
}
}
for(int i=0;i<tail;i++){
int x=q[i];
for(int t=0;t<(int)G2[x].size();t++){
f[x][j]=f[x][j]+f[G2[x][t]][j];
if(f[x][j]>=p)f[x][j]-=p;
}
}
//for(int i=1;i<=n;i++)printf("f[%d][%d]=%d\n",i,j,f[i][j]);
}
int ans=0;
for(int i=0;i<=k;i++){
ans+=f[1][i];
if(ans>=p)ans-=p;
}
printf("%d\n",ans);
}
return 0;
}
void Dijkstra(int x){
memset(dis,63,sizeof(dis));
memset(vis,0,sizeof(vis));
priority_queue<A>heap;
dis[x]=0;
heap.push(A(x,0));
while(!heap.empty()){
x=heap.top().x;
heap.pop();
vis[x]=true;
for(int i=0;i<(int)G[x].size();i++)if(!vis[G[x][i]]&&dis[G[x][i]]>dis[x]+W[x][i]){
dis[G[x][i]]=dis[x]+W[x][i];
heap.push(A(G[x][i],dis[G[x][i]]));
}
}
}
void bfs(){
head=tail=0;
for(int i=1;i<=n;i++)if(!du[i])q[tail++]=i;
memset(vis,0,sizeof(vis));
while(head!=tail){
int x=q[head++];//printf("%d ",x);
vis[x]=true;
for(int i=0;i<(int)RG2[x].size();i++)if(!--du[RG2[x][i]])q[tail++]=RG2[x][i];
}
//printf("\n");
}
D2T1 奶酪
\(O(n^2)\)暴力就行了,水题。
D2T2 宝藏
看到数据范围一眼\(O^*(3^n)\)状压DP,其中\(3^n\)来自枚举子集的子集。做法好像有很多,比如ryf的做法就比我快了10倍……日渐辣鸡的窝
定义\(f_{i,j,S}\)表示以\(i\)为根的子树,\(i\)在整棵树里的深度是\(j\),集合\(S\)中的所有点都在这棵子树中时这棵子树的最小总代价。
不难写出转移方程:
\]
(这个转移方程是在枚举与\(i\)相邻的一个点\(k\)并把以\(k\)为根的子树分出去)
然而你发现这个DP是\(O(n^3 3^n)\)的,并不能跑过去。
考虑优化转移,如果把转移方程中与\(S\)无关的部分拿出来并定义一个辅助数组
\]
的话,我们就可以把状态转移方程改写成
\]
这样就可以把复杂度降到\(O(n^2 3^n)\)了,由于常数不大,并不需要卡常。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define bit(x) (1<<((x)-1))
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,w[15][15],f[15][15][(1<<12)+1],g[15][15][(1<<12)+1];
int main(){
scanf("%d%d",&n,&m);
memset(w,63,sizeof(w));
memset(f,63,sizeof(f));
memset(g,63,sizeof(g));
while(m--){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
w[x][y]=w[y][x]=min(w[x][y],z);
}
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j][bit(i)]=0;
for(int j=n-1;j;j--)for(int s=0;s<(1<<n);s++)for(int i=1;i<=n;i++){
for(int k=1;k<=n;k++)if((bit(k)|s)==s&&w[i][k]<INF)
g[i][j][s]=min(g[i][j][s],f[k][j+1][s]+w[i][k]*j);
for(int t=s&(s-1);;(--t)&=s){
f[i][j][s]=min(f[i][j][s],f[i][j][s^t]+g[i][j][t]);
if(!t)break;
}
}
int ans=INF;
for(int i=1;i<=n;i++)ans=min(ans,f[i][1][(1<<n)-1]);
printf("%d",ans);
return 0;
}
D2T3 列队
这题真是劲啊……比往年的数据结构NB到不知哪儿去了……
(码力太差还没写出来,写出来之后再补题解)
NOIP2017 题解的更多相关文章
- [NOIP补坑计划]NOIP2017 题解&做题心得
终于做完了…… 场上预计得分:?(省一分数线:295) 由于看过部分题解所以没有预计得分qwq 题解: D1T1 小凯的疑惑 题面 震惊!一道小学奥数题竟难倒无数高中考生! 欢迎大家以各种姿势*和谐* ...
- NOIP2017题解
T1小凯的疑惑 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的.现在小 凯想知道在无法准确支付的物品中, ...
- NOIP2017 题解(给自己看的) --有坑要填
目录 D1T1精妙证明: D1T3 D2T2 几道水题就不写了.... D1T1精妙证明: 把ax+by = z 的z按照模a剩余系分类 由于\((a,b)=1\)所以对于每个\(k\in[0, a) ...
- 【NOIP题解】NOIP2017 TG D2T3 列队
列队,NOIP2017 TG D2T3. 树状数组经典题. 题目链接:洛谷. 题意: Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. ...
- NOIP2017 列队 题解报告【56行线段树】
题目描述 Sylvia 是一个热爱学习的女♂孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有n \times mn×m名学生,方阵的行数 ...
- noip2017爆炸记——题解&总结&反省(普及组+提高组)
相关链接: noip2018总结 noip2017是我见过的有史以来最坑爹的一场考试了. 今年北京市考点有一个是我们学校,我还恰好被分到了自己学校(还是自己天天上课的那个教室),于是我同时报了普及提高 ...
- 题解[NOIP2017] 列队
题解[NOIP2017] 列队 题面 解析 看到这题时感觉这个编号很难维护啊? 后来看了lzf大佬的题解才会.. 首先,考虑一个稍微暴力的做法, 维护每一行的前\(m-1\)个人和最后一列的\(n\) ...
- 【题解】NOIP2017 提高组 简要题解
[题解]NOIP2017 提高组 简要题解 小凯的疑惑(数论) 不讲 时间复杂度 大力模拟 奶酪 并查集模板题 宝藏 最优解一定存在一种构造方法是按照深度一步步生成所有的联通性. 枚举一个根,随后设\ ...
- 【题解】NOIP2017逛公园(DP)
[题解]NOIP2017逛公园(DP) 第一次交挂了27分...我是不是必将惨败了... 考虑这样一种做法,设\(d_i\)表示从该节点到n节点的最短路径,\(dp(i,k)\)表示从\(i\)节点 ...
随机推荐
- SQL 必知必会 总结(一)
SQL必知必会 总结(一) 第 1 课 了解SQL 1.数据库(database): 保存有组织的数据容器(通常是一个文件或一组文件). 2.数据库管理系统(DBMS): 数据库软件,数据库是通过 D ...
- 手机端API接口验证及参数签名验证
问题背景: 后端服务对手机APP端开放API,没有基本的校验就是裸奔,别人抓取接口后容易恶意请求,不要求严格的做的安全,但是简单的基础安全屏障是要建立的,再配合HTTPS使用,这样使后端服务尽可能的安 ...
- P2149 Elaxia的路线
P2149 Elaxia的路线 题意简述: 在一个n(n<=1500)个点的无向图里找两对点之间的最短路径的最长重合部分,即在保证最短路的情况下两条路径的最长重合长度(最短路不为一) 思路: 两 ...
- JavaScript父子页面之间的相互调用
父页面: <!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head>< ...
- 03——Solr学习之Solr的使用(不会用)
1.先放上次在linux搭建成功的solr管理UI界面 2.有个很蛋疼的问题我就要吐槽一下了 由于没接触过solr这玩意,在百度上一顿操作搜索怎么用,怎么导入数据,建索引库什么的,看了一大片别人的博客 ...
- android图片压缩总结
一.bitmap 图片格式介绍 android中图片是以bitmap形式存在的,那么bitmap所占内存,直接影响到了应用所占内存大小,首先要知道bitmap所占内存大小计算方式: bitmap内存大 ...
- vmware不显示usb图标解决办法
一.1.打开service.msc服务. 2.在“服务”中启动“VMware USB Arbitration Service”服务项. 3.重启vmware 二.如果有安全软件要设置vmware ...
- java学习-zxing生成二维码矩阵的简单例子
这个例子需要使用google的开源项目zxing的核心jar包 core-3.2.0.jar 可以百度搜索下载jar文件,也可使用maven添加依赖 <dependency> <gr ...
- xgboost使用
xgboost的实现方式为多颗CART树,其实xgboost就是类似于随机森林,但是与随机森林不同,他不是多个子树决策的结果,CART树最后会算出一个得分,是一个值,最后算出分类的时候,是多个值结合在 ...
- expect安装和使用
Expect是一个我们常在shell交互时常用到的工具,它主要由expect-send组成.Expect是等待输出内容中的特定字符.然后由send发送特定的相应.Expect的工作流程类似于:小明和小 ...