眾所周知,最短路算法在比賽中占有相當部分的分值
在大多數情況下,甚至使用並非最佳的算法也可以的得到相當大部分的分數。
以下選自書中核心內容,是競賽生要熟練掌握且清晰理解的幾種最基本算法。
(全部化為有向圖做,雙向邊就化為兩條單向邊,恩,就這樣操作)
以下所有討論不考慮環,全部INF處理,請悉知。
一.Dijkstra算法(貪心)(O(n^2))(效率一般,但相當可做)(邊權非負,否則。。。qwq)
1.dist[1]=0 , 其余 dist = INF
->2.找出一個未標記,dist[x]最小的節點x,標記x。
->3.掃描節點x的所有出邊(x,y,z),if (dist[y]>dist[x]+z ) dist[y]=dist[x]+z (這是這個最基本的算法的核心語句,具體可想象三角形三邊關系)。
(注意這里邊權為負數的話,那么我們2中最先選擇出的起點就不一定最小了,那萬一以后跑出來個負邊,全局都會受影響,那咱還貪個啥心,還跑個啥Dijkstra)
4.重復2-3直到全部點都被標記(233要是所有點走的線路都試過了,沒有答案你來打我啊)
二.Bellman-Ford算法(O(nm))(我不咋用,但這個是SPFA的原型)
->1.掃描所有邊(x,y,z),if (dist[y]>dist[x]+z)dist[y]=dist[x]+z (我沒看錯吧?我把上文抄了下來?沒錯,幾種算法的基本套路是一樣的)
(但要注意,這里不同的是這里Bell-ford的2並非像Dijkstra那樣要求對所有點掃描,而僅僅是某一部分。)
(為神魔呢?這是樣做有道理的。證明:若一張有向圖的一條邊滿足三角形不等式,即dist[y]<=dist[x]+z,那么所有這樣的邊連起來的一條路肯定最短啦,干嘛還要把所有邊都跑一邊呢,這樣就剪掉很多不必要去掃的邊)
2.重復,直到1那家伙掃完 (即基於三角不等式的關系下不再發生任何更新)。
那么,就這樣愉快而簡單地結束啦233。(松弛,向最優)
注意到這里的不同:Dijkstra側重基於點的掃描(直到全部點標記完),而Bell-ford側重基於邊的關系(直到不再發生更新),這也是兩者設計的不同。(粗鄙見解)
但是這就結束了嗎?ccf的樣例可不會山吧干修(誤),我們來把Bellman-Ford優化得到一個更低時間復雜度的版本。
怎么優化呢,加一個隊列吧,讓這個隊列只有待擴展的點,每次都是滿足三角不等式才入隊,避免了Bellman-ford去掃那些不需要的邊。(冗余掃描)
這樣在稀疏的有向圖上會更快,只在密圖或特殊圖上退化為朴素的Bellmand-ford。
三.SPFA算法(O(km))
1.建立一個隊列,最初只含起點。
->2.取出隊頭x,掃描其幾個出邊(x,y,z),If (dist[y]>dist[x]+z ) dist[y]=dist[x]+z.把y入隊(已在隊中就不用了)(其實dist一直在記錄起點到該點的距離,而我們的更新,就是在走不同路徑時記錄下該點到起點更近的距離)
3.重復2直到隊列空。
(怎么樣?基於隊列操作的Bellman-ford是不是一下子省去很多不用掃的內容啊)
好了,到這里關於單源最短路(SSSP)的算法都復習完了。
相信是個像我這種笨蛋也思路清晰了。
那么,我們來敲下熟悉到吐了的板子吧233
敲板子是一件愉快的事(因為不用動腦子啊,理解題意后胡亂選個合適的算法,敲敲板子,再針對題中具體情況小小地改動一下,做做特殊處理,然后這種語文題就交給板子去跑啦qwq。大多數情況下還是可以直接拿到不錯的部分分數,相信再調調細節聰明的你就可以ac啦)。
來自lrd書上的幾個板子
熟悉一下鄰接表 空間復雜度O(n+m)
ver(V---點)記錄每條邊的終點,edge(E---邊)記錄對應每條邊的邊權。
head記錄從每個節點出發第一條邊在ver和edge數組中儲存的位置。
next是下一條邊在ver和edge中的位置。

1 void add(int x,int y,int z){ 2 ver[++tot]=y,edge[tot]=z; 3 next[tot]=head[x],head[x]=tot; 4 } 5 //加邊 6 7 for(int i=head[x];i;i=next[i]){ 8 int y=ver[i],z=edge[i]; 9 。。。。。。 10 } 11 //訪問從x出發的邊
(LXL不對該段代碼負責qwq)
Dijkstra板子(用於稠密圖更快)

1 int a[3010][3010],d[3010],n,m; 2 bool v[3010]; 3 4 void dijkstra(){ 5 memset(d,0x3f,sizeof(d));//dist數組 6 memset(v,o,sizeof(v));//節點標記 7 d[1]=0;//漏了這句可是跑不了的哦。 8 for(int i=1;i<n;i++){ 9 int x=0;//源 10 for(int j=1;j<=n;j++) if(!v[j]&&(x==0||d[j]<d[x])) x=j;//找剩余未掃描點中更近的點 11 v[x]=1;// 12 for(int y=1;y<=n;y++) d[y]=min(d[y],d[x]+a[x][y]);//有沒有找回動態規划的感覺,沒錯,是初戀的感覺。 13 //當然,這里只是更新下每個 <點到源> 的最小距離。 14 } 15 //重復n-1次掃描全部節點 16 } 17 18 int main(){ 19 cin>>n>>m; 20 memset(a,0x3f,sizeof(a)); 21 for(int i=1;i<=n;i++) a[i][i]=0; 22 for(int i=1;i<=m;i++){ 23 int x,y,z; 24 scanf("%d%d%d",&x,&y,&z); 25 a[x][y]=min(a[x][y],z); 26 //在建圖時已經開始預處理下 27 } 28 dijkstra(); 29 for(int i=1;i<=n;i++) 30 printf("%d\n",d[i]);//記錄了所有最終態的距離,每個點到源的最短距。 31 return 0; 32 }

1 const int N=100010,M=1000010; 2 int head[N],ver[N],edge[M],Next[M],d[N]; 3 bool v[N]; 4 int n,m,tot; 5 priority_queue<pair<int,int>> q; 6 void add(int x,int y,int z){ 7 ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot; 8 } 9 void dijkstra(); 10 int main(){ 11 cin>>n>>m; 12 for(int i=1;i<=m;i++){ 13 int x,y,z; 14 scanf("%d%d%d",&x,&y,&z); 15 add(x,y,z); 16 } 17 dijkstra(); 18 for(int i=1;i<=n;i++){ 19 printf("%d\n",d[i]); 20 } 21 return 0; 22 } 23 void dijkstra(){ 24 memset(d,0x3f,sizeof(d)); 25 memset(v,0,sizeof(v)); 26 d[1]=0; 27 q.push(make_pair(0,1)); 28 while(q.size()){ 29 int x=q.top().second;q.pop(); 30 if(v[x]) continue; 31 v[x]=1; 32 //注意這里的運行原理是找到當前的最短距離(貪心),繼續更新 33 for(int i=head[x];i;i=Next[i]){ 34 int y=ver[i],z=edge[i]; 35 if(d[y]>d[x]+z) { 36 d[y]=d[x]+z; 37 q.push(make_pair(-d[y],y)); 38 } 39 } //整段在當前全局最小值的基礎上去更新其他d值 40 }//原理是用二叉堆來節省find全局最小值的時間。 41 42 }
上面注釋寫了不少廢話(甚至描述有偏差),請直接無視。
SPFA板子(用於稀疏圖更快)

1 const int N=100010,M=1000010; 2 int head[N],ver[M],edge[M],Next[M],d[N]; 3 int n,m,tot; 4 queue<int>q; 5 bool v[N]; 6 void add(int x,int y,int z){ver[++tot]=y,edge[tot]=z,Next[tot]=head[x],head[x]=tot;} 7 void spfa(){ 8 memset(d,0x3f,sizeof(d)); 9 memset(v,0,sizeof(v)); 10 d[1]=0;v[1]=1; 11 q.push(1); 12 while(q.size()){ 13 int x=q.front();q.pop(); 14 v[x]=0; 15 for(int i=head[x];i;i=Next[i]){ 16 int y=ver[i],z=edge[i]; 17 if(d[y]>d[x]+z){ 18 d[y]=d[x]+z; 19 if(!v[y]) q.push(y),v[y]=1; 20 } 21 } 22 } 23 } 24 int main(){ 25 cin>>n>>m; 26 for(int i=1;i<=m;i++){int x,y,z;scanf("%d%d%d",&x,&y,&z);add(x,y,z);} 27 spfa(); 28 for(int i=1;i<=n;i++) printf("%d\n",d[i]); 29 return 0; 30 }