分層圖最短路
一個聽起來就很高端的詞,其實也沒有聽起來那么可怕啦。
關於這道題的小故事: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