分层图最短路
分层图最短路
一个听起来就很高端的词,其实也没有听起来那么可怕啦。
关于这道题的小故事:loli说要从头讲输入输出!于是我们被赶到了高一高二的机房,学姐说:我给你推荐道题吧...
我自己想到这个做法的时候是这么做的,将所有的点,所有的边都建出来,非常好做,但是占的内存比较大。其实,因为每一层图非常相似,所以可以用一个二维数组直接做最短路。
$dp[i][j]$表示第$i$个点,第$j$层的最短路。用第$i$层更新第$i+1$层就可以啦。
[SHOI2012]回家的路:https://www.luogu.org/problemnew/show/P3831
题意概述:在一个$n*n$的网格图中跑地铁,每坐一站路就耗2分钟,如果想从横向改为纵向,必须从一些特殊的点进行换乘,每次换乘耗时1分钟。给定起点终点,求最短路。
多么有趣的题目啊!又是多么的难啊!一开始想了一个很奇妙的思路,开两个Dijkstra互相跑,然而我的思路很乱并没有成功。
写了一个小时后放弃信心开始胡思乱想,想到以前有一次坐火车从东站到西站要跨越整个城市,感觉就像是两个火车站一样呢...两个火车站吗?奇迹般的想到了怎么做这道题。
把每一个换乘点拆成两个点,一个点负责跑横向路,一个点负责跑纵向路,两个点之间再连一条边,为换乘代价,这样就转化成了一个普通的最短路问题。注意$firs$数组要开到$m$的两倍以上,邻接表要开到$m$的五倍以上(每个点拆开后连两条边,每个点再向外连出两条边)。还有一点小的细节,起点和终点拆开后须连一条边权为0的边(因为本来就是一个点)。
这道题...我用正解思路得了暴力分60,为什么呢?(其实是因为建图)一开始怎么也想不出怎么连边才比较快,于是就用了$m^{2}$,后来才想起来先排个序啊...
# include <cstdio> # include <iostream> # include <cstring> # include <queue> # include <algorithm> using namespace std; const int maxm=100005; const int inf=1e8; struct edge { int nex,too,co; }g[maxm*15]; struct poin { int r; int x,y; }M[maxm]; int h=0,ld,las,n,m,x,y,firs[maxm<<1]; int d[maxm*2+5]; bool vis[maxm*2+5]={false}; int sx,sy,tx,ty; queue <int> q; void add(int x,int y,int co) { g[++h].co=co; g[h].too=y; g[h].nex=firs[x]; firs[x]=h; g[++h].co=co; g[h].too=x; g[h].nex=firs[y]; firs[y]=h; return ; } void dis(int s) { for (int i=0;i<=m*2+4;i++) d[i]=inf; d[s]=0; vis[s]=1; q.push(s); int beg,j; while (q.size()) { beg=q.front(); q.pop(); vis[beg]=0; for (int i=firs[beg];i;i=g[i].nex) { j=g[i].too; if(d[beg]+g[i].co>=d[j]) continue; d[j]=d[beg]+g[i].co; if(!vis[j]) { vis[j]=1; q.push(j); } } } } bool cmpa(poin a,poin b) { if(a.x==b.x) return a.y<b.y; return a.x<b.x; } bool cmpb(poin a,poin b) { if(a.y==b.y) return a.x<b.x; return a.y<b.y; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=m;i++) { scanf("%d%d",&M[i].x,&M[i].y); M[i].r=i; } scanf("%d%d%d%d",&sx,&sy,&tx,&ty); int aaa=10000000,bbb,ccc=10000000,ddd; sort(M+1,M+1+m,cmpa); for (int i=1;i<=m;i++) { if(M[i].x==sx) { if(max(M[i].y-sy,sy-M[i].y)*2<=aaa) aaa=min(aaa,max(M[i].y-sy,sy-M[i].y)*2),bbb=M[i].r; } if(M[i].x==tx) { if(max(M[i].y-ty,ty-M[i].y)*2<=ccc) ccc=min(ccc,max(M[i].y-ty,ty-M[i].y)*2),ddd=M[i].r; } add(M[i].r,M[i].r+m+2,1); if(M[i-1].x!=M[i].x) continue; add(M[i].r,M[i-1].r,(M[i].y-M[i-1].y)*2); } if(aaa!=10000000) add(0,bbb,aaa); if(ccc!=10000000) add(m+1,ddd,ccc); sort(M+1,M+1+m,cmpb); aaa=10000000; ccc=10000000; for (int i=1;i<=m;i++) { if(M[i].y==sy) { if(max(M[i].x-sx,sx-M[i].x)*2<=aaa) aaa=min(aaa,max(M[i].x-sx,sx-M[i].x)*2),bbb=M[i].r+m+2; } if(M[i].y==ty) { if(max(M[i].x-tx,tx-M[i].x)*2<=ccc) ccc=min(ccc,max(M[i].x-tx,tx-M[i].x)*2),ddd=M[i].r+m+2; } if(M[i-1].y!=M[i].y) continue; add(M[i].r+m+2,M[i-1].r+m+2,(M[i].x-M[i-1].x)*2); } if(aaa!=10000000) add(m+2,bbb,aaa); if(ccc!=10000000) add(2*m+3,ddd,ccc); if(sx==tx) { printf("%d",max(sy-ty,ty-sy)*2); return 0; } if(sy==ty) { printf("%d",max(sx-tx,tx-sx)*2); return 0; } add(0,m+2,0); add(2*m+3,m+1,0); dis(0); if(inf!=d[m+1]) printf("%d",d[m+1]); else printf("-1"); return 0; }
发现分层图真是个好东西,终于解决这道题后就去学了一下。
时隔半个月我又回来写分层图啦!
[JLOI2011]飞行路线:https://www.luogu.org/problemnew/show/P4568
题意概述:给定一张无向图,可以将其中$k$条边的权值改为$0$,求$1$到$n$的最短路。(k<=10)
$k$非常小啊,于是可以拆点,把一个点强行拽成k个,然后就可以连边了。怎么连呢?首先原来就有的边是不能不连的,而且还要在每一层图上都连。接下来就要确定每层图之间的关系了,从第i层到第i+1层的边边权全为0,等于说是用掉了一次免费卡,于是愉快的连一连,跑一跑堆优化$dijktra$,这道题就做完啦。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <queue> 5 # include <cstring> 6 # define mp make_pair 7 # define R register int 8 9 using namespace std; 10 11 int h,n,m,k,s,t,a,b,c,firs[220009]; 12 struct edge 13 { 14 int co,too,nex; 15 }g[4200009]; 16 int d[220009]; 17 bool vis[220009]; 18 typedef pair <int,int> pii; 19 priority_queue <pii,vector<pii>,greater<pii> > q; 20 21 void add(int x,int y,int co) 22 { 23 g[++h].too=y; 24 g[h].co=co; 25 g[h].nex=firs[x]; 26 firs[x]=h; 27 } 28 29 void dis() 30 { 31 memset(d,127,sizeof(d)); 32 d[s]=0; 33 q.push(mp(d[s],s)); 34 int beg,j; 35 while (q.size()) 36 { 37 beg=q.top().second; 38 q.pop(); 39 if(vis[beg]) continue; 40 vis[beg]=true; 41 for (R i=firs[beg];i;i=g[i].nex) 42 { 43 j=g[i].too; 44 if(d[beg]+g[i].co>=d[j]) continue; 45 d[j]=d[beg]+g[i].co; 46 q.push(mp(d[j],j)); 47 } 48 } 49 } 50 51 int main() 52 { 53 scanf("%d%d%d",&n,&m,&k); 54 for (R i=1;i<=m;++i) 55 { 56 scanf("%d%d%d",&a,&b,&c); 57 add(a,b,c); 58 add(b,a,c); 59 for (R j=1;j<=k;++j) 60 { 61 add(j*n+a,j*n+b,c); 62 add(j*n+b,j*n+a,c); 63 add((j-1)*n+a,j*n+b,0); 64 add((j-1)*n+b,j*n+a,0); 65 } 66 } 67 s=1,t=n; 68 dis(); 69 int ans=d[t]; 70 for (R i=0;i<=k;++i) 71 ans=min(ans,d[i*n+t]); 72 cout<<ans; 73 return 0; 74 }
发现用二维$d$数组更快更好写。
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # define mp make_pair 6 # define R register int 7 8 using namespace std; 9 10 int h,n,m,k,s,t,a,b,c,firs[10009]; 11 struct edge 12 { 13 int co,too,nex; 14 }g[200009]; 15 int d[10009][12]; 16 bool vis[10009][12]; 17 typedef pair <int,int> pii; 18 priority_queue <pii,vector<pii>,greater<pii> > q; 19 20 void add(int x,int y,int co) 21 { 22 g[++h].too=y; 23 g[h].co=co; 24 g[h].nex=firs[x]; 25 firs[x]=h; 26 } 27 28 void dis() 29 { 30 memset(d,127,sizeof(d)); 31 d[s][0]=0; 32 q.push(mp(0,s)); 33 int beg,j,x; 34 while (q.size()) 35 { 36 beg=q.top().second; 37 q.pop(); 38 x=beg/n; 39 beg%=n; 40 if(vis[beg][x]) continue; 41 vis[beg][x]=true; 42 for (R i=firs[beg];i;i=g[i].nex) 43 { 44 j=g[i].too; 45 if(d[beg][x]+g[i].co<d[j][x]) 46 { 47 d[j][x]=d[beg][x]+g[i].co; 48 q.push(mp(d[j][x],j+n*x)); 49 } 50 if(x==k) continue; 51 if(d[j][x+1]>d[beg][x]) 52 { 53 d[j][x+1]=d[beg][x]; 54 q.push(mp(d[j][x+1],j+(x+1)*n)); 55 } 56 } 57 } 58 } 59 60 int main() 61 { 62 scanf("%d%d%d",&n,&m,&k); 63 scanf("%d%d",&s,&t); 64 for (R i=1;i<=m;++i) 65 { 66 scanf("%d%d%d",&a,&b,&c); 67 add(a,b,c); 68 add(b,a,c); 69 } 70 dis(); 71 printf("%d\n",d[t][k]); 72 return 0; 73 }
改造路:https://www.luogu.org/problemnew/show/P2939
题意概述:与上一题一模一样。
1 # include <cstdio> 2 # include <iostream> 3 # include <queue> 4 # include <cstring> 5 # define mp make_pair 6 # define R register int 7 8 using namespace std; 9 10 int h,n,m,k,s,t,a,b,c,firs[220009]; 11 struct edge 12 { 13 int co,too,nex; 14 }g[4200009]; 15 int d[220009]; 16 bool vis[220009]; 17 typedef pair <int,int> pii; 18 priority_queue <pii,vector<pii>,greater<pii> > q; 19 20 void add(int x,int y,int co) 21 { 22 g[++h].too=y; 23 g[h].co=co; 24 g[h].nex=firs[x]; 25 firs[x]=h; 26 } 27 28 void dis() 29 { 30 memset(d,127,sizeof(d)); 31 d[s]=0; 32 q.push(mp(d[s],s)); 33 int beg,j; 34 while (q.size()) 35 { 36 beg=q.top().second; 37 q.pop(); 38 if(vis[beg]) continue; 39 vis[beg]=true; 40 for (R i=firs[beg];i;i=g[i].nex) 41 { 42 j=g[i].too; 43 if(d[beg]+g[i].co>=d[j]) continue; 44 d[j]=d[beg]+g[i].co; 45 q.push(mp(d[j],j)); 46 } 47 } 48 } 49 50 int main() 51 { 52 scanf("%d%d%d",&n,&m,&k); 53 for (R i=1;i<=m;++i) 54 { 55 scanf("%d%d%d",&a,&b,&c); 56 add(a,b,c); 57 add(b,a,c); 58 for (R j=1;j<=k;++j) 59 { 60 add(j*n+a,j*n+b,c); 61 add(j*n+b,j*n+a,c); 62 add((j-1)*n+a,j*n+b,0); 63 add((j-1)*n+b,j*n+a,0); 64 } 65 } 66 s=1,t=n; 67 dis(); 68 int ans=d[t]; 69 for (R i=0;i<=k;++i) 70 ans=min(ans,d[i*n+t]); 71 cout<<ans; 72 return 0; 73 }
孤岛营救问题:https://www.luogu.org/problemnew/show/P4011
题意概述:有一个$n \times m$的网格,有些格子之间有门,需要对应的钥匙来打开,有的格子里面有钥匙,必须走到那里才可以拿到,保证钥匙不超过$10$种,求从$(1,1)$走到$(n,m)$的最小步数.
钥匙当然是可以重复使用的...玩魔塔的后遗症...
注意到钥匙不超过$10$种是一个很强的条件,可以用来进行状压.用$d[i][j][k]$表示当前在$(i,j)$这个点上,持有的钥匙的状态为$k$的最小步数,把转移条件考虑清楚后直接上堆优化$dijkstra$即可.
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <cstring> 5 # include <queue> 6 # define mp make_pair 7 # define R register int 8 9 using namespace std; 10 11 const int dx[]={-1,1,0,0}; 12 const int dy[]={0,0,-1,1}; 13 const int maxn=11; 14 int n,m,p,a,b,c,d,s,k,maxp; 15 int ans=-1; 16 int g[maxn][maxn][4]; 17 int dis[maxn][maxn][4250]; 18 int vis[maxn][maxn][4250]; 19 int key[maxn][maxn]; 20 struct z 21 { 22 int val,x,y,k; 23 bool operator > (const z &a) const { 24 return val>a.val; 25 } 26 bool operator < (const z &a) const { 27 return val<a.val; 28 } 29 }; 30 priority_queue <z,vector<z>,greater<z> > q; 31 32 bool mw (int x,int y,int k,int d) 33 { 34 int xx=x+dx[d]; 35 int yy=y+dy[d]; 36 if(xx<=0||xx>n||yy<=0||yy>m) return false; 37 if((g[x][y][d]&k)==g[x][y][d]) return true; 38 return false; 39 } 40 41 void dij() 42 { 43 int x,y,k,xx,yy; 44 z a,b; 45 a.x=1,a.y=1,a.k=key[1][1],a.val=0; 46 q.push(a); 47 while (q.size()) 48 { 49 a=q.top(); 50 q.pop(); 51 x=a.x; 52 y=a.y; 53 k=a.k; 54 if(vis[x][y][k]) continue; 55 vis[x][y][k]=true; 56 a.k|=key[x][y]; 57 if(dis[x][y][a.k]>dis[x][y][k]) 58 { 59 dis[x][y][a.k]=dis[x][y][k]; 60 q.push(a); 61 continue; 62 } 63 k=a.k; 64 for (R d=0;d<4;++d) 65 { 66 if(!mw(x,y,k,d)) continue; 67 xx=x+dx[d]; 68 yy=y+dy[d]; 69 if(dis[xx][yy][k]<=a.val+1) continue; 70 b.x=xx; 71 b.y=yy; 72 b.k=k; 73 b.val=a.val+1; 74 dis[xx][yy][k]=a.val+1; 75 q.push(b); 76 } 77 } 78 } 79 80 int main() 81 { 82 scanf("%d%d%d",&n,&m,&maxp); 83 memset(dis,1,sizeof(dis)); 84 scanf("%d",&k); 85 for (R i=1;i<=k;++i) 86 { 87 scanf("%d%d%d%d%d",&a,&b,&c,&d,&p); 88 for (R j=0;j<4;++j) 89 if(a+dx[j]==c&&b+dy[j]==d) g[a][b][j]|=(1<<p),g[c][d][j^1]|=(1<<p); 90 } 91 scanf("%d",&s); 92 for (R i=1;i<=s;++i) 93 { 94 scanf("%d%d%d",&a,&b,&c); 95 key[a][b]|=(1<<c); 96 } 97 dis[1][1][ key[1][1] ]=0; 98 dij(); 99 ans=-1; 100 for (R i=0;i<=(1<<(maxp+1));++i) 101 if(dis[n][m][i]<dis[0][0][0]) 102 { 103 if(ans==-1) ans=dis[n][m][i]; 104 else ans=min(ans,dis[n][m][i]); 105 } 106 printf("%d",ans); 107 return 0; 108 }
冻结:https://www.lydsy.com/JudgeOnline/problem.php?id=2662
题意概述:给定一张$n$个点$m$条边的图,有$k$次机会可以将某条路的长度变为原来的一半,求从$1$到$n$的最小花费.
首先可以将问题改为每次只可能将下一步要走的边距离改短,因为提早改其实没有什么用处,所以就是一个分层图最短路的板子啦.
这个插入代码的功能好像...坏了?
---shzr