自TG滾粗後咕咕咕了這麼久,最近從新開始學OI,也會慢慢開始更博了。。。。node
最短路算法經典的就是SPFA(Bellman-Ford),Dijkstra,Floyd;ios
本期先講兩個經典的單源最短路算法:算法
首先是我最喜(hao)歡(xie)的SPFA(惋惜常常被卡)數組
SPFA:
Warning:SPFA在OI競賽中慎用,極易容易被卡!!!優化
基本流程:
從起點開始,每次將掃到的點入隊,每一個點遍歷全部與其相連的點,並更新最短路,若是該點未入隊,則將其入隊;spa
均攤複雜度爲$ O(KE) $(K=2),但由於SPFA在網格圖中入隊次數過多,致使卡成原始的Bellman-Ford($ o(VE) $),因此競賽中不經常使用到(但仍是要學會的);code
醜陋的代碼(洛谷P3371):blog
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 int n,m,x,y,z,tot,f[500001],q[1000001],h,t,s,first[500001],nxt[500001],last[500001],en[500001],len[500001]; 5 //f爲記錄最短路徑的數組,q爲隊列; 6 //first,nxt,last,en,len爲平平無奇的鄰接表; 7 bool b[10001]; 8 //判斷是否在隊列中 9 void add(int x,int y,int z) 10 { 11 ++tot; 12 if(first[x]==0) first[x]=tot; else nxt[last[x]]=tot; 13 en[tot]=y; 14 len[tot]=z; 15 last[x]=tot; 16 } 17 void SPFA(int x) 18 { 19 int k=first[x]; 20 do 21 { 22 if((long long)len[k]+f[x]<f[en[k]]) 23 { 24 f[en[k]]=len[k]+f[x]; 25 if(not b[en[k]]) 26 { 27 ++t; 28 q[t]=en[k]; 29 b[en[k]]=true; 30 } 31 } 32 k=nxt[k]; 33 } 34 while(k!=0); 35 } 36 int main() 37 { 38 scanf("%d%d%d",&n,&m,&s); 39 for(int i=1;i<=m;++i) 40 { 41 scanf("%d%d%d",&x,&y,&z); 42 add(x,y,z); 43 } 44 for(int i=1;i<=n;++i) 45 f[i]=2147483647; 46 f[s]=0; 47 h=0; 48 t=0; 49 SPFA(s); 50 while(h<=t) 51 { 52 SPFA(q[h]); 53 b[q[h]]=false; 54 ++h; 55 } 56 for(int i=1;i<=n;++i) 57 printf("%d ",f[i]); 58 return 0; 59 }
Dijkstra:
最經常使用的最短路算法(蒟蒻的我最近才學會太菜了);隊列
基本流程:
定義兩個點的集合,S爲已求出最短路的點集,T爲未求出最短路的點集;it
每次在T集合中找到一個Dis值最小的點,將其放入S集合中,並用它更新其餘點的最短路;
由此,樸素的Dijkstra在每次選擇操做中暴力枚舉每一個點複雜度爲$ O(V) $,更新時的複雜度也爲$ O(V) $,及時間複雜度爲$ O(V^2) $;
聰(AK)明(IOI)的讀者可能已經發現了,選擇Dis值最小的點時,暴力枚舉過於浪費,爲了不超時,咱們能夠用堆(或者線段樹)優化爲$ o(Vlog(E)) $;
每次選擇時,直接訪問堆的根節點,將其彈出,並把更新的節點壓入堆中;(每次壓入堆中沒必要要判是否已入堆,只須要開個bool數組記錄該點是否已有最短路便可)
(甚至能夠用斐波那契堆優化爲$ o(E+Vlog(V)) $)
附上醜陋的代碼(CodeChef CLIQUED):
1 #include<cstdio> 2 #include<queue> 3 #include<iostream> 4 using namespace std; 5 const int MAXN=1600010; 6 const long long INF=1e15+10; 7 struct node 8 { 9 int pos; 10 long long dis; 11 bool operator <(const node &x)const 12 { 13 return x.dis<dis; 14 } 15 }; 16 priority_queue<node> q; 17 int tot,first[MAXN],nxt[MAXN],last[MAXN],to[MAXN],len[MAXN]; 18 //平平無奇的鄰接表 19 int T,n,k,x,m,s; 20 long long dis[MAXN]; 21 //dis爲到每一個點的最短路徑 22 bool vis[MAXN]; 23 //vis記錄每一個點是否已有最短路的值 24 void dijistra() 25 { 26 dis[s]=0; 27 q.push((node){s,0}); 28 while(!q.empty()) 29 { 30 node t=q.top(); 31 int po=t.pos; 32 long long di=t.dis; 33 q.pop(); 34 //取堆的根節點 35 if(vis[po]) 36 continue; 37 //若是已有最短路,說明該點已更新過,無需更新 38 vis[po]=1; 39 for(int i=first[po];i;i=nxt[i]) 40 if(di+len[i]<dis[to[i]]) 41 { 42 dis[to[i]]=di+len[i]; 43 q.push((node){to[i],di+len[i]}); 44 } 45 //常規鬆弛(更新最短路徑) 46 } 47 } 48 void add(int x,int y,int z) 49 { 50 tot++; 51 if(first[x]==0) 52 first[x]=tot; 53 else 54 nxt[last[x]]=tot; 55 last[x]=tot; 56 to[tot]=y; 57 len[tot]=z; 58 } 59 int main() 60 { 61 scanf("%d",&T); 62 for(int ii=1;ii<=T;++ii) 63 { 64 scanf("%d%d%d%d%d",&n,&k,&x,&m,&s); 65 n++; 66 for(int i=1;i<=n;++i) 67 first[i]=0; 68 for(int i=1;i<=m;++i) 69 { 70 int x,y,z; 71 scanf("%d%d%d",&x,&y,&z); 72 add(x,y,z); 73 add(y,x,z); 74 } 75 for(int i=1;i<=k;++i) 76 { 77 add(i,n,x); 78 add(n,i,0); 79 } 80 for(int i=1;i<=n;++i) 81 dis[i]=INF; 82 for(int i=1;i<=n;++i) 83 vis[i]=0; 84 dijistra(); 85 for(int i=1;i<n;++i) 86 printf("%lld ",dis[i]); 87 printf("\n"); 88 } 89 return 0; 90 }