顧名思義,「分層圖最短路」就是在多層平行的圖上跑最短路node
分層圖最短路的模型就是在最短路模型的基礎上加上k個決策c++
最短路模型:給定n個點m個條路,求從s出發到t的最短距離數組
分層圖最短路模型:給定n個點m條路以及k個決策,再求出s到t的最短距離學習
k個決策不會影響圖的結構,只會影響當前的代價或狀態spa
(PS:對於每一道題,決策的具體內容是不同的,能夠結合以後的例題理解).net
面對分層圖最短路這種題,咱們通常有兩種方法解決:code
直接構建k+1層平行的圖blog
多開一維記錄決策信息get
面對每一層,咱們仍是先像普通最短路同樣連邊建圖博客
處理層與層,咱們將有邊相連的兩個點u、v各自多向下一層連一條邊(權值視題目的決策內容而定,而且是有向邊,向下的有向邊,這樣就模擬出了k次決策!)
當有n個點時,(1 ~ n)表示第一層,(1+n)~(n+n)爲第二層,(1+2 * n)~(n+2 * n)爲第三層·······(1+i * n)~(n+i * n)爲第i+1層
由第3點可得:由於要建k+1層圖,因此數組要開到n * ( k + 1),點的個數也爲n * ( k + 1 )
有點抽象,咱們來舉個栗子: n=4,m=3,k=2 0 1 100 1 2 100 2 3 100 建成的k+1層圖以下:
如今給出完整的模板code:
#include <bits/stdc++.h> using namespace std; int n,m,k,u,v,w,s,t,tot; int dis[5000010],vis[5000010],head[5000010]; //根據題意改變數組大小 priority_queue<pair<int,int> > shan; struct node { int to,net,val; }e[5000010]; inline void add(int u,int v,int w) { //鏈式前向星存邊 e[++tot].val=w; e[tot].to=v; e[tot].net=head[u]; head[u]=tot; } inline void dijkstra(int s) { //Dijkstra跑最短路的板子 memset(dis,0x3f,sizeof dis); dis[s]=0; shan.push(make_pair(0,s)); while(!shan.empty()) { int x=shan.top().second; shan.pop(); if(vis[x]) continue; vis[x]=1; for(register int i=head[x];i;i=e[i].net) { int v=e[i].to; if(dis[v]>dis[x]+e[i].val) { dis[v]=dis[x]+e[i].val; shan.push(make_pair(-dis[v],v)); } } } } int main() { scanf("%d%d%d",&n,&m,&k); scanf("%d%d",&s,&t); for(register int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); //正常存邊 add(v,u,w); for(register int j=1;j<=k;j++) { //構建以後的k層圖 add(u+j*n,v+j*n,w); //每一層的連邊同上正常連邊 add(v+j*n,u+j*n,w); add(u+(j-1)*n,v+j*n,0); //層與層之間的聯繫 add(v+(j-1)*n,u+j*n,0); //層與層邊的權值不必定是0!要視題目而定 } } for(register int i=1;i<=k;i++) { //將每一層的終點特別連起來 add(t+(i-1)*n,t+i*n,0); } dijkstra(s); printf("%d",dis[t+k*n]); //最終答案存在最後一層的終點處 return 0; }
由於我認爲第一種作法比較簡單,就沒怎麼編寫第二種作法的代碼(並且兩種作法面對不卡數據的題目選任意一種都能過)
因此如今就給出我學習的博客,你們能夠看這個連接自行學習第二種作法(我的感受有點相似於DP思想)
PS:個人第一種作法代碼和上面的博客有些許的區別,但願你們區分開來,不要記混了qwq
這道題徹底就是分層圖最短路題型的模板題
注意一點就是這題的編號是從0~(n-1)的,因此爲了處理方便,咱們在輸入後就進行加一操做,轉換爲1~n的編號
如今給出主程序代碼(Dijkstra部分見上面的模板):
int main() { scanf("%lld%lld%lld",&n,&m,&k); scanf("%lld%lld",&s,&t); s++;t++; //由於編號從0開始,方便處理都加1,下面的u++、v++同理 for(register long long i=1;i<=m;i++) { scanf("%lld%lld%lld",&u,&v,&w); u++;v++; add(u,v,w); add(v,u,w); for(register long long j=1;j<=k;j++) { add(u+j*n,v+j*n,w); add(v+j*n,u+j*n,w); add(u+(j-1)*n,v+j*n,0); add(v+(j-1)*n,u+j*n,0); //由於該題的決策時免費,因此權值爲0 } } for(register long long i=1;i<=k;i++) { add(t+(i-1)*n,t+i*n,0); } dijkstra(s); printf("%lld",dis[t+k*n]); return 0; }
這題也是直接套模板就能A掉的,並且規定了起點是1終點是n,雙倍經驗get!
哦哦哦,補充一下,題意就是求從1到n的最短路距離,不是輸出對哪些小徑進行升級ovo!
直接給主程序代碼:
int main() { scanf("%d%d%d",&n,&m,&k); for(register int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&t); add(u,v,t); add(v,u,t); for(register int j=1;j<=k;j++) { add(u+j*n,v+j*n,t); add(v+j*n,u+j*n,t); add(u+(j-1)*n,v+j*n,0); add(v+(j-1)*n,u+j*n,0); } } for(register int i=1;i<=k;i++) { add(i*n,(i+1)*n,0); //由於每一層的終點就是n,因此改寫成這樣,注意一下區別ovo } dijkstra(); printf("%d",dis[(k+1)*n]); return 0; }
這道題95%都是板子,只有一點不一樣:本題的決策內容是花費減半,因此層與層之間的權值再也不是0,而是這條邊本來權值的一半!
其餘的就沒什麼好說的,三倍經驗get!
給出主程序代碼以下:
int main() { scanf("%d%d%d",&n,&m,&k); for(register int i=1;i<=m;i++) { scanf("%d%d%d",&u,&v,&t); add(u,v,t); add(v,u,t); for(register int j=1;j<=k;j++) { add(u+j*n,v+j*n,t); add(v+j*n,u+j*n,t); add(u+(j-1)*n,v+j*n,t/2); //注意區別哦!這裏的權值再也不是0,而是一半的花費! add(v+(j-1)*n,u+j*n,t/2); } } for(register int i=1;i<=k;i++) { add(i*n,(i+1)*n,0); //同上一道題,由於每一層的終點就是n,因此改寫成這樣 } dijkstra(); printf("%d",dis[(k+1)*n]); return 0; }
初看這道題容易直接當作純板子題,可是你會發現程序過不了樣例:答案是4,本身的輸出是5
再去讀題,請注意這句話:「總費用決定於其中最長的電話線的長度」,說明不是求從1到n的最短路,而是求從1到n的路徑中最大邊權最小,因此咱們須要改一下Dijkstra的入隊判斷:
if(dis[v]>max(dis[x],e[i].val)) { dis[v]=max(dis[x],e[i].val); shan.push(make_pair(-dis[v],v)); }
if(dis[(k+1)*n]>1000001) printf("-1"); else printf("%d",dis[(k+1)*n]); 由於每條路的邊權值不會超過1000000(題目規定)
最後,以上只是我對於「分層圖最短路」的基本學習記錄,有任何理解錯誤的地方,還煩請各位dalao指出,蒟蒻感激涕零啊orz!