分層圖求最短路
1495:【例 2】孤島營救問題
分層最短路做。以獲取鑰匙的狀態建立分層圖,然後BFS就行了
https://blog.csdn.net/a_pathfinder/article/details/100537489 裡面寫了BFS+狀壓 和 最短路得解法
like 汽車加油行駛問題(另一個分層圖的問題)
要先算出最多邊數:1>>10*10*10=1024000;
把原來矩陣每個座標存成一個點,再對可到鄰邊賦權值1,在建圖的時候,我們只要連線一個雙向邊即可,假設j是從i左移一格,那麼i就是j右移一格。上下同理。
值得注意的是,我們每次在一層的時候,在沒有鑰匙i的時候,我們需要把i鑰匙鎖在的點u,連線到有了i鑰匙的那一層對應的位置上v,權值是0,這樣就建成了1>>種類 的圖,
ps:要記錄總層數的總點數,之後dis[]的初始化時所有點。
//下面是最短路得做法,我還沒看 /* 這裡涉及到的變化還蠻多的,我們要先算出最多邊數:1>>10*10*10=1024000; 把原來矩陣每個座標存成一個點,再對可到鄰邊賦權值1,在建圖的時候,我們只要連線一個雙向邊即可,假設j是從i左移一格,那麼i就是j右移一格。上下同理。 值得注意的是,我們每次在一層的時候,在沒有鑰匙i的時候,我們需要把i鑰匙鎖在的點u,連線到有了i鑰匙的那一層對應的位置上v,權值是0,這樣就建成了1>>種類 的圖, u(鎖在的位置)--->v(鑰匙的位置),權值為0 並且圖直接有連線。最後跑一遍最短路即可。 ps:要記錄總層數的總點數,之後dis[]的初始化時所有點。 */ //分層求最短路 #include<bits/stdc++.h> using namespace std; const int N = 12; const int M = 1024100; const int INF = 0x3f3f3f3f; typedef pair<int,int> P; struct keyn{ int x,y; }key[N][20];//key[i][j] 種類為i的鑰匙第j把的座標 int n,m,p,s,k,cnt,layer,nn,nsum;//n寬,m長,p種類,s總鑰匙數,k總障礙數,cnt計數器,layer層數,nn每層的點,nsum總點數 //layer = 1<<p; //層數為 2^(鑰匙種類數) //nn = n*m; //每一層點數 //nsum = n*m*layer; //總共點數 int num[N][N],fg[200][200]; int head[M],nex[M],ver[M],edge[M]; int hadk[N],kn[N],vis[M],dis[M]; void add(int x,int y,int w){ ver[++cnt] = y; nex[cnt] = head[x]; edge[cnt] = w; head[x] = cnt; } void read(){ cnt = 0; int x,y; scanf("%d%d%d%d",&n,&m,&p,&k); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) num[i][j] = ++cnt; ///把每個左邊當成一個點 for(int i=1,g,u,v;i<=k;i++){ scanf("%d%d",&x,&y); u = num[x][y]; //把兩個座標連成一條邊 scanf("%d%d",&x,&y); v = num[x][y]; scanf("%d",&g); if(g==0) g=-1; fg[u][v] = fg[v][u] = g; } scanf("%d",&s); for(int i = 1,q;i <= s;i++){ scanf("%d%d%d",&x,&y,&q); kn[q]++; //這種鑰匙的數量 key[q][kn[q]].x = x; key[q][kn[q]].y = y; } } void build(){ layer = 1<<p; //層數為 2^(鑰匙種類數) nn = n*m; //每一層點數 nsum = n*m*layer; //總共點數 for(int t=0;t<layer;t++){ for(int i=1;i<=p;i++){ if(t&(1<<(i-1))) hadk[i] = 1; else hadk[i] = 0; } for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ int u = num[i][j],v = num[i][j+1];//向右連邊 if(v && fg[u][v]!=-1) if(fg[u][v]==0 || hadk[fg[u][v]]){ //可以走或者是有鑰匙 就連邊 add(t*nn+u,t*nn+v,1); add(t*nn+v,t*nn+u,1); } v = num[i+1][j]; //向下連邊 if(v && fg[u][v]!=-1) if(fg[u][v]==0 || hadk[fg[u][v]]){ //可以走或者是有鑰匙 就連邊 add(t*nn+u,t*nn+v,1); //層數*每一層點數 add(t*nn+v,t*nn+u,1); } } for(int i=1;i<=p;i++){ if(!hadk[i]) //沒有鑰匙才可以移動狀態 for(int j=1;j<=kn[i];j++){ //如果這一層沒有這樣的鑰匙,那麼就把這種所對應的鑰匙的地方連線,邊權為0 int u = num[key[i][j].x][key[i][j].y]; add(t*nn+u,( t|(1<<(i-1)) ) *nn+u,0); } } } } void dj(){ priority_queue<P> q; for(int i = 0;i <= nsum; i++) dis[i] = INF; q.push(make_pair(0,1)),dis[1] = 0; //first是距離,second是位置 while(q.size()){ int u = q.top().second; q.pop(); if(vis[u]) continue; vis[u] = 1; for(int i = head[u];i;i = nex[i]){ int v = ver[i],w=edge[i]; if(dis[v] > dis[u] + w){ dis[v] = dis[u] + w; q.push(make_pair(-dis[v],v)); } } } } void spfa(){ queue<int> q; for(int i = 0;i <= nsum;i++) dis[i] = INF; q.push(1),dis[1] = 0,vis[1] = 1; while(q.size()){ int u = q.front(); q.pop();vis[u] = 0; for(int i=head[u];i;i = nex[i]){ int v = ver[i],w=edge[i]; if(dis[v] > dis[u] + w){ dis[v] = dis[u] + w; if(!vis[v]){ q.push(v),vis[v] = 1; } } } } } void solve(){ int ans = INF; for(int i =0;i<layer;i++) ans = min(ans,dis[i*nn+num[n][m]]); if(ans==INF) printf("-1\n"); else printf("%d\n",ans); } int main(){ read(); build(); //spfa(); dj(); solve(); return 0; }
1496:【例 3】架設電話線
這個不是求最短路了,而是需要求出第k+1最短邊
分層求最短路
我們把節點擴充套件到二維,二元組(x,p)代表一個節點,從(x,p)到(y,p)上有一個長為w的邊,(x,p)到(y,p+1)上有長度為0的邊,最後對所有層求最短路,
分層的時候只能從低層到高層連大家都知道吧,因為你不能說我把一個邊變成0後再變回去。
同一層邊權是多少就是多少,不同層就是0
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1010; const int maxm=2e5+10; const int INF=0x3fffffff; typedef long long LL; /* 第二種:分層求最短路 我們把節點擴充套件到二維,二元組(x,p)代表一個節點,從(x,p)到(y,p)上有一個長為w的邊,(x,p)到(y,p+1)上有長度為0的邊,最後對所有層求最短路, 分層的時候只能從低層到高層連大家都知道吧,因為你不能說我把一個邊變成0後再變回去。 */ typedef pair<int,int> pp; int n,m,k,cnt; int head[maxm],to[maxm],wei[maxm],next[maxm]; int vis[maxm],dis[maxm]; void add(int x,int y,int z){ to[++cnt]=y; wei[cnt]=z; next[cnt]=head[x]; head[x]=cnt; } void inti(){ scanf("%d %d %d",&n,&m,&k); for(int i=1;i<=m;i++){ int x,y,z; scanf("%d %d %d",&x,&y,&z); add(x,y,z); add(y,x,z); for(int j=1;j<=k;j++){ //同一層滴 add(j*n+x,j*n+y,z);add(j*n+y,j*n+x,z); //不是同一層的 add((j-1)*n+x,j*n+y,0);add((j-1)*n+y,j*n+x,0); } } } void dij1(){ priority_queue<pp,vector<pp>,greater<pp> > q; for(int i=0;i<=maxm;i++) dis[i]=INF; dis[1]=0; q.push(make_pair(0,1)); while(!q.empty()){ int op=q.top().second; //下標 q.pop(); if(vis[op]) continue; vis[op]=1; for(int i=head[op];i;i=next[i]){ int v=to[i]; int w=wei[i]; if(dis[v]>max(dis[op],w)){ //最大值 dis[v]=max(dis[op],w); if(!vis[v]) q.push(make_pair(dis[v],v)); } } } } int main(){ inti(); dij1(); if(dis[k*n+n]==INF) printf("-1\n"); else printf("%d\n",dis[k*n+n]); return 0; }
1502:汽車加油行駛問題
這道題也可以用分層圖解決 +spfa(也就這個好些一點
也可以用普通的bfs+spfa解決(我更喜歡這個wwww
即總共建k+1層圖,只有層與層之間有邊,汽車每走一步就會向上移動一層。建邊規則滿足題目要求即可。
因為k是能走的長度,但是我還是不太能理解建邊的過程【這個建邊好麻煩】
#include<bits/stdc++.h> #define N 200005 using namespace std; int Map[105][105]; int num[105][105][15]; struct ss { int v,next,w; }; ss edg[N*4]; int head[N],now_edge=0; void addedge(int u,int v,int w) { edg[now_edge]=(ss){v,head[u],w}; head[u]=now_edge++; } int dis[N]; int vis[N]={0}; void spfa() { for(int i=0;i<N;i++)dis[i]=INT_MAX/2; dis[num[1][1][0]]=0; //節點(離散後) queue<int>q; q.push(num[1][1][0]); vis[num[1][1][0]]=1; while(!q.empty()) { int now=q.front(); q.pop(); vis[now]=0; for(int i=head[now];i!=-1;i=edg[i].next) { int v=edg[i].v; if(dis[v]>dis[now]+edg[i].w) { dis[v]=dis[now]+edg[i].w; if(!vis[v]) { q.push(v); vis[v]=1; } } } } } int main() { int n,k,a,b,c; memset(head,-1,sizeof(head)); scanf("%d %d %d %d %d",&n,&k,&a,&b,&c); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++)scanf("%d",&Map[i][j]); //1為有油庫,0為沒有 int cnt=1; for(int kk=0;kk<=k;kk++) { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) num[i][j][kk]=cnt++; //轉化為節點 } //0--1層 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(i+1<=n)addedge(num[i][j][0],num[i+1][j][1],0); if(j+1<=n)addedge(num[i][j][0],num[i][j+1][1],0); if(i-1>=1)addedge(num[i][j][0],num[i-1][j][1],b); if(j-1>=1)addedge(num[i][j][0],num[i][j-1][1],b); } //(1~k-1)--(2~k)層 for(int kk=1;kk<k;kk++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!Map[i][j]) //沒有油庫 { if(i+1<=n)addedge(num[i][j][kk],num[i+1][j][kk+1],0); if(j+1<=n)addedge(num[i][j][kk],num[i][j+1][kk+1],0); if(i-1>=1)addedge(num[i][j][kk],num[i-1][j][kk+1],b); if(j-1>=1)addedge(num[i][j][kk],num[i][j-1][kk+1],b); addedge(num[i][j][kk],num[i][j][0],c+a); //沒有油庫,還需要加上c } else //有油庫的話,就只需要費用a並且是從0走到kk層 { addedge(num[i][j][kk],num[i][j][0],a); } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!Map[i][j]) { addedge(num[i][j][k],num[i][j][0],c+a); //沒有油庫,還需要加上c } else { addedge(num[i][j][k],num[i][j][0],a);//有油庫的話,就只需要費用a並且是從0走到kk層 } spfa(); int ans=INT_MAX; for(int i=0;i<=k;i++)ans=min(ans,dis[num[n][n][i]]); //求最小 printf("%d\n",ans); return 0; }
洛谷:
P3831 [SHOI2012]回家的路
https://www.luogu.com.cn/problem/P3831
先把題目讀懂,換乘表示可以轉彎(我一開始沒有意識到
洛谷的題解都寫得很好
經典的分層圖最短路裸題,在此簡要介紹:
我們可能遇到這樣的圖論模型:在一個正常的圖上可以進行 kk 次決策,對於每次決策,不影響圖的結構,隻影響目前的狀態或代價。
同時這個圖論模型和經典的最短路有關,這樣我們可以考慮運用分層圖最短路。
此題為樸素的裸題,在此僅介紹一種(時空非最優)的易於理解的實現方法:(我懶得再寫一遍了
此題的決策為轉向,由於只存在橫向和縱向兩個放學,我們對這兩個方向分別建立一層。即一層只連原圖橫向邊,一層只連縱向邊。
對於轉向這個決策,將決策前的狀態和決策後的狀態間連線一條權值為決策代價的邊,表示付出該代價轉換了狀態。
在本題中,即上下兩層對應點連線一條權值為1的邊,層內邊權均為2.
然後跑最短路即可。
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=200010; const int maxm=800010; const int INF=0x3f3f3f3f; typedef long long LL; typedef unsigned long long ull; int red(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } return x*f; } int head[maxm],cnt=1; struct node{ int to,nex,wei; }ed[maxm]; void adde(int x,int y,int z){ ed[cnt].to=y; ed[cnt].nex=head[x]; ed[cnt].wei=z; head[x]=cnt++; ed[cnt].to=x; ed[cnt].nex=head[y]; ed[cnt].wei=z; head[y]=cnt++; } struct node1{ int x,y,id; }a[maxn]; //兩個比較函式 bool cmp1(node1 a,node1 b){ //根據x來排序 if(a.x==b.x) return a.y<b.y; return a.x<b.x; } bool cmp2(node1 a,node1 b){ //根據y來排序 if(a.y==b.y) return a.x<b.x; return a.y<b.y; } int n,m; queue<int> q; int d[maxn],S,T; bool vis[maxn]; void spfa(){ for(int i=1;i<=2*m+4;i++) d[i]=INF; d[S]=0; vis[S]=1; q.push(S); while(!q.empty()){ int op=q.front(); q.pop(); vis[op]=0; for(int i=head[op];i;i=ed[i].nex){ int t=ed[i].to; if(d[t]>d[op]+ed[i].wei){ d[t]=d[op]+ed[i].wei; if(!vis[t]){ vis[t]=1; q.push(t); } } } } } int main(){ n=red(); m=red(); //s是m+1,t是m+2 S=m+1;T=m+2; for(int i=1;i<=m+2;i++){ a[i].x=red(); a[i].y=red(); a[i].id=i; } sort(a+1,a+m+3,cmp1); //x排的 for(int i=1;i<m+2;i++){ if(a[i].x==a[i+1].x) adde(a[i].id,a[i+1].id,2*(a[i+1].y-a[i].y)); } sort(a+1,a+m+3,cmp2); //y排的 for(int i=1;i<m+2;i++){ //兩層之間的節點應該區分,所以都要加上m+2 if(a[i].y==a[i+1].y) adde(a[i].id+2+m,a[i+1].id+m+2,2*(a[i+1].x-a[i].x)); } for(int i=1;i<=m;i++){ //所有的中轉站 兩層之間連線,權值為1 adde(i,i+2+m,1); } //兩層之間的起點與起點,終點與終點連線,權值為0 adde(m+1,2*m+3,0); adde(m+2,2*m+4,0); spfa(); if(d[T]==INF){ printf("-1"); return 0; } printf("%d",d[T]); return 0; } //
P4568 [JLOI2011]飛行路線
https://www.luogu.com.cn/problem/P4568
這道題和一本通上面的第k最短路很像,也是分層圖的模板題
//各層內部正常連邊,各層之間從上到下連權值為0的邊。每向下跑一層,就相當於免費搭一次飛機。跑一遍從s到t+n*k+t的最短路即可
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=110005; const int INF=0x3fffffff; typedef long long LL; typedef unsigned long long ull; //這道題和一本通上面的第k最短路很像,也是分層圖的模板題 //各層內部正常連邊,各層之間從上到下連權值為0的邊。每向下跑一層,就相當於免費搭一次飛機。跑一遍從s到t+n*k+t的最短路即可 int n,m,k,s,t; struct node{ int to,nex,val; }ed[2500001]; //這個範圍是怎麼求得 int head[maxn],cnt; int vis[maxn],dis[maxn]; void adde(int x,int y,int z){ ed[++cnt].nex=head[x];ed[cnt].to=y;ed[cnt].val=z; head[x]=cnt; } typedef pair<int,int> pp; void dij(int st){ memset(dis,0x3f,sizeof(dis)); dis[st]=0; priority_queue<pp,vector<pp>,greater<pp> > q; q.push(make_pair(0,st)); while(!q.empty()){ int x=q.top().second; q.pop(); if(vis[x]) continue; vis[x]=1; for(int i=head[x];i;i=ed[i].nex){ int t=ed[i].to,wei=ed[i].val; if(dis[t]>dis[x]+wei){ dis[t]=dis[x]+wei; q.push(make_pair(dis[t],t)); } } } } int main(){ scanf("%d %d %d %d %d",&n,&m,&k,&s,&t); int x,y,z; for(int i=1;i<=m;i++){ scanf("%d %d %d",&x,&y,&z); adde(x,y,z);adde(y,x,z); for(int j=1;j<=k;j++){ adde(x+(j-1)*n,y+j*n,0); //不同層 adde(y+(j-1)*n,x+j*n,0); adde(x+j*n,y+j*n,z); adde(y+j*n,x+j*n,z); } } //這一步:防止hack資料 for(int i=1;i<=k;i++) adde(t+(i-1)*n,t+i*n,0); dij(s); printf("%d",dis[t+k*n]); return 0; }