最短路擴展例題。html
POJ3767node
在一個國家有兩個group,記作1和2,N個city,每一個city屬於1或者2。每兩個city間有必定的距離,如今要從city1去city2,問最短的距離是多少,要求至多隻有一次穿越時跨過度屬不一樣group的city。city1老是屬於group1,city2總屬於group2。c++
分析:同一城市內建雙向邊,city1到city2建單向邊,注意無解。spa
洛谷P2505code
有向圖,求每條邊被多少條最短路通過htm
分析:定理:任意一條最短路的某個子路徑都是最短路(懶得證,腦補吧)blog
枚舉起點,先跑一遍dj(珍愛生命,遠離spfa),把全部屬於最短路的邊標記,造成最短路圖,一定是有向無環圖。排序
考慮到每一條邊的貢獻爲兩端端點的最短路條數相乘;ci
拓撲排序,先求cnt1[ i ]表示從起點到 i 節點的最短路條數,易得若是cnt1[ st ]=1且存在邊從u到v 那麼cnt1[ v ] += cnt1[ u ];get
再求從cnt2[ i ]表示以 i 爲起點的最短路條數,cnt2[ i ]初始值爲1,若存在邊從u到v,則cnt2[ u ]+=cnt2[ v ];
每條邊每次貢獻爲cnt1[ u ] * cnt2[ v ];
給定一張帶權圖,求從s到t的剛好通過k條邊的最短路徑長度
分析:用一個矩陣表示初始通過一條邊a_ij的長度,沒有邊是正無窮
矩陣乘法:a矩陣表示通過n條邊,b矩陣表示通過m條邊,a*b矩陣則表示通過n+m條邊,採用了floyd的思想
矩陣快速冪便可
證實:不會,可是會甩連接《矩陣乘法在信息學的應用》
拓展:求s到t長度爲k的方案數:
矩陣a [ d ][ i ][ j ]爲從i到j長度爲d的方案數;
有一種圖論模型:在圖上能夠進行k次特殊決策,每次決策不改變圖的結構,只改變當前狀態,咱們稱之爲分層圖最短路。
有k次免費機會的最短路
分析:咱們在存儲每個狀態時多存儲一個變量f表示當前已經使用了幾回免費機會,dis[ i ][ j ]表示到 i 節點用了 j 次機會的最短路長度;
每次決策分兩部分:
第一部分:不使用機會,直接莽過去;
第二部分:使用機會,用特殊代價轉移狀態;
#include<bits/stdc++.h>
using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,k,res=1e15; int st,ed; int dis[100010][21]; bool vis[10010][21]; int head[100010],cnt; struct point { int nxt,to,val; }a[100010]; struct p { int now,fre,dis; bool operator <(const p &a) const { return dis>a.dis; } }; priority_queue<p> q; inline void add(int x,int y,int z) { a[++cnt].nxt=head[x]; a[cnt].to=y; a[cnt].val=z; head[x]=cnt; } inline void dj() { memset(dis,0x3f,sizeof(dis)); dis[st][0]=0; q.push((p){st,0,0}); while(!q.empty()) { int now=q.top().now; int f=q.top().fre; q.pop(); if(vis[now][f]) continue; vis[now][f]=1; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; if(f<k&&dis[t][f+1]>dis[now][f]) { dis[t][f+1]=dis[now][f]; if(t==ed) res=min(res,dis[t][f+1]); q.push((p){t,f+1,dis[t][f+1]}); } if(dis[t][f]>dis[now][f]+a[i].val) { dis[t][f]=dis[now][f]+a[i].val; if(t==ed) res=min(res,dis[t][f]); q.push((p){t,f,dis[t][f]}); } } } } signed main() { n=read(),m=read(),k=read(); st=read(),ed=read(); for(int x,y,z,i=1;i<=m;++i) { x=read(),y=read(),z=read(); add(x,y,z); add(y,x,z); } dj(); printf("%lld\n",res); return 0; }
一個網格圖,走一條邊花費爲2,在某些點轉向花費爲1,求最短路;
分析狀態:位置,方向。與其餘最短路不一樣的就是方向。
考慮到往回走確定不優,因此咱們只須要記錄是橫向仍是縱向便可。
考慮到n較大,咱們只記錄有用的節點(轉向點和起點終點),將全部有用點記錄在結構體中,設每一個點的編號爲(x-1)*n+y;
1.按編號排序,離散化;
2.按x排序,x相等按y排序,把x相等的相鄰兩個點之間連上邊;
3.按y排序,y相等按x排序,把y相等的相鄰兩個點之間連上邊;
跑dj(珍愛生命,遠離spfa)
(數據太水沒有沒法到達的狀況,實際上注意判斷,雖然我沒判就過了)
#include<bits/stdc++.h>
using namespace std; #define int long long inline int read() { int x=0,f=1; char ch; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } int n,m,res=1e18; int stx,sty,edx,edy,tot,st,ed; int dis[200010][3]; bool vis[200010][3]; int head[233333],cnt; struct node { int x,y,id; }sub[200010]; struct p { int dis,ch,now; bool operator<(const p &a) const { return dis>a.dis; } }; priority_queue<p> q; struct point { int nxt,to,val; int ch; }a[500010]; inline void add(int x,int y,int z,int ch) { a[++cnt].nxt=head[x]; a[cnt].to=y; a[cnt].val=z; a[cnt].ch=ch; head[x]=cnt; } inline bool cmp(node a,node b) { return a.id<b.id; } inline bool cmp1(node a,node b) { return (a.x==b.x)?a.y<b.y:a.x<b.x; } inline bool cmp2(node a,node b) { return (a.y==b.y)?a.x<b.x:a.y<b.y; } inline void dj() { memset(dis,0x3f,sizeof(dis)); for(int i=1;i<=2;++i) { dis[st][i]=0; q.push((p){0,i,st}); } while(!q.empty()) { int now=q.top().now; int ch=q.top().ch; q.pop(); if(vis[now][ch]) continue; vis[now][ch]=1; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; int f=a[i].ch; if(dis[t][f]>dis[now][ch]+a[i].val+(f!=ch)) { dis[t][f]=dis[now][ch]+a[i].val+(f!=ch); if(t==ed) res=min(res,dis[t][f]); q.push((p){dis[t][f],f,t}); } } } } signed main() { n=read(),m=read(); for(int x,y,i=1;i<=m;++i) { x=read(),y=read(); sub[++tot].x=x; sub[tot].y=y; sub[tot].id=(x-1)*n+y; } stx=read(),sty=read(),edx=read(),edy=read(); sub[++tot].x=stx; sub[tot].y=sty; sub[tot].id=(stx-1)*n+sty; sub[++tot].x=edx; sub[tot].y=edy; sub[tot].id=(edx-1)*n+edy; sort(sub+1,sub+tot+1,cmp); for(int i=1;i<=tot;++i) { if(sub[i].id==(stx-1)*n+sty) { sub[i].id=i,st=i; } else if(sub[i].id==(edx-1)*n+edy) { sub[i].id=i,ed=i; } else sub[i].id=i; } sort(sub+1,sub+tot+1,cmp1); for(int i=1;i<=tot;++i) { if(sub[i-1].x==sub[i].x) { add(sub[i].id,sub[i-1].id,(sub[i].y-sub[i-1].y)*2,1); add(sub[i-1].id,sub[i].id,(sub[i].y-sub[i-1].y)*2,1); } } sort(sub+1,sub+tot+1,cmp2); for(int i=1;i<=tot;++i) { if(sub[i-1].y==sub[i].y) { add(sub[i].id,sub[i-1].id,(sub[i].x-sub[i-1].x)*2,2); add(sub[i-1].id,sub[i].id,(sub[i].x-sub[i-1].x)*2,2); } } dj(); //-1
printf("%lld\n",res); return 0; }
求嚴格次短路。
方法1:dis[ i ][ 1 ]爲到 i 節點最短路值,dis [ i ][ 2 ]爲到 i 節點次短路值;
這個用spfa,dj我每研究出按什麼排序比較好
inline void spfa() { for(int i=1;i<=n;++i) dis[i][1]=dis[i][2]=1e15; dis[1][1]=0; q.push(1); while(!q.empty()) { int now=q.front(); q.pop(); vis[now]=0; for(int i=head[now];i;i=a[i].nxt) { int t=a[i].to; bool flag=0; if(dis[t][1]>dis[now][1]+a[i].val) { dis[t][2]=dis[t][1]; dis[t][1]=dis[now][1]+a[i].val; flag=1; } if(dis[t][2]>dis[now][1]+a[i].val&&dis[t][1]!=dis[now][1]+a[i].val) { dis[t][2]=dis[now][1]+a[i].val,flag=1; } if(dis[t][2]>dis[now][2]+a[i].val) { dis[t][2]=dis[now][2]+a[i].val; flag=1; } if(flag&&!vis[t]) { vis[t]=1; q.push(t); } } } }
方法2:求出最短路和路徑,枚舉刪邊;
方法3:A*,不會;
給定一張無向圖, 要求一棵最小生成樹。 根結點肯定爲1,權值的定義爲:每一個結點都有本身的權值,每條邊有長度, 構建每條邊的花費是,此邊的單價 * 全部後代的結點權重之和。
轉而考慮每一個節點花費:該節點權值 * 節點到根的距離;
從1號節點跑一遍最短路就好了
妙啊
BZOJ4144(權限題,本身找吧qwq)
n個點m條邊的帶權無向圖,有s個點是加油站,每輛車有個油量上限c,保證x和y是加油站,可否從x走到y;
分析:咱們發現除了加油站,其餘點都是垃圾都沒有用,因此在加油站之間搞MST,而後貨車運輸就行了(別告訴我你不會貨車運輸)
怎麼肯定邊權呢?
咱們記 f [ i ]爲距離 i 號節點最近的加油站;
考慮若是從x到y的路徑上出現一個點k,f[ k ]屬於z,那麼從x到z,再用z到y必定更優;
把全部加油站都扔進堆裏面跑一遍dj,求出全部點的dis和 f ;
枚舉每一條邊,若是有一條邊兩端的節點屬於兩個不一樣的加油站,那麼那麼就在待定最小生成樹圖上增長一條鏈接u,v的邊,權值爲dis[u]+dis[v]+len(u,v);