圖論_最短路例題(更新中)
P4779 【模板】單源最短路徑(標準版)
題目描述
給定一個n個點,m條有向邊的帶非負權圖,請你計算從s出發,到每個點的距離。
資料保證你能從s出發到任意點。
輸入格式
第一行為三個正整數n, m, s。 第二行起m行,每行三個非負整數ui, vi, wi,表示從ui到vi有一條權值為wi的有向邊。
輸出格式
輸出一行n個空格分隔的非負整數,表示s到每個點的距離。
輸入輸出樣例
輸入 #1 輸出 #1
4 6 1 0 2 4 3 1 2 2 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4
說明/提示
1≤n≤1e5;
1≤m≤2e5;
s = 1;
1≤ui,vi≤n;
0≤wi≤1e9,
0≤∑wi≤1e9。
思路:dijkstra演算法
0分程式碼:#1#3~#5WA #2#6~#10TLE
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <queue> 7 #include <map> 8 #include <set> 9 using namespace std;10 const int maxn = 5e5 + 5; 11 const long long INF = 2147483647; 12 struct edge 13 { 14 int from; 15 int to; 16 int cost; 17 }es[maxn]; 18 int V,E,s,u,v,w; 19 long long d[maxn]; 20 bool used[maxn]; 21 void fill(bool* start, bool* end, bool flag) 22 { 23 for (; start != end; ++start) 24*start = flag; 25 } 26 void fill(long long* start, long long* end, long long num) 27 { 28 for (; start != end; ++start) 29 *start = num; 30 } 31 void init() 32 { 33 fill(d + 1, d + 1 + V, INF); 34 fill(used + 1, used + 1 + V, false); 35 d[s] = 0; 36 } 37 void dijkstra(int s) 38 { 39 init(); 40 while (true) 41 { 42 int v = -1; 43 for (int u = 1; u <= V; ++u) 44 if (!used[u] && (v == -1 || d[u] < d[v])) 45 v = u; 46 if (v == -1) 47 break; 48 used[v] = true; 49 for (int u = 1; u <= V; ++u) 50 { 51 for (int i = 1; i <= E; ++i) 52 { 53 if (es[i].from == v && es[i].to == u) 54 { 55 w = es[i].cost; 56 break; 57 } 58 } 59 d[u] = min(d[u],d[v] + w); 60 } 61 } 62 } 63 int main() 64 { 65 scanf("%d%d%d",&V,&E,&s); 66 for (int i = 1; i <= E; ++i) 67 { 68 scanf("%d%d%d",&u,&v,&w); 69 es[i].from = u; 70 es[i].to = v; 71 es[i].cost = w; 72 } 73 dijkstra(s); 74 for (int i = 1; i < V; ++i) 75 { 76 printf("%lld ",d[i]); 77 } 78 printf("%lld",d[V]); 79 return 0; 80 }
分析原因:本題我看資料範圍1e5 如果使用鄰接矩陣必定炸空間;
所以使用edge結構體,結果顯然導致了另一個問題,但我這個蒻蒟就是沒注意到,
那便是查詢cost[v][u]時,由於結構體的原因不能O(1)查到,導致要多套一重迴圈來查詢
這直接導致了TLE
優化:有一說一,STL真好用。
定義的一個struct edge用於接受臨時資料,其實沒啥卵用;
vector<pair<int,int> > v;建立邊權鄰接表;
priority_queue<pii, vector<pii>,greater<pii> > pq;使用優先佇列維護
100分程式碼:#1~#10 AC
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <queue> 7 #include <map> 8 #include <set> 9 using namespace std; 10 const int maxn = 5e5 + 5; 11 const long long INF = 2147483647; 12 int V,E,s,u,v,w; 13 long long d[maxn]; 14 //bool used[maxn]; 15 struct edge 16 { 17 int cost; 18 int to; 19 }; 20 typedef pair<int,int> pii; 21 vector<pii> G[maxn]; 22 /* 23 void fill(bool* start, bool* end, bool flag) 24 { 25 for (; start != end; ++start) 26 *start = flag; 27 } 28 */ 29 void fill(long long* start, long long* end, long long num) 30 { 31 for (; start != end; ++start) 32 *start = num; 33 } 34 void init() 35 { 36 fill(d + 1, d + 1 + V, INF); 37 //fill(used + 1, used + 1 + V, false); 38 d[s] = 0; 39 } 40 void dijkstra(int s) 41 { 42 init(); 43 priority_queue<pii, vector<pii>, greater<pii> > pq; 44 pq.push(pii(0,s)); 45 while (!pq.empty()) 46 { 47 pii p = pq.top(); 48 pq.pop(); 49 int v = p.second; 50 if (d[v] < p.first) 51 continue; 52 for (int i = 0; i < (int)G[v].size(); ++i) 53 { 54 edge e; 55 e.cost = G[v][i].first; 56 e.to = G[v][i].second; 57 if (d[e.to] > d[v] + e.cost) 58 { 59 d[e.to] = d[v] + e.cost; 60 pq.push(pii(d[e.to],e.to)); 61 } 62 } 63 } 64 } 65 int main() 66 { 67 scanf("%d%d%d",&V,&E,&s); 68 for (int i = 1; i <= E; ++i) 69 { 70 scanf("%d%d%d",&u,&v,&w); 71 G[u].push_back(pii(w,v)); 72 } 73 dijkstra(s); 74 for (int i = 1; i < V; ++i) 75 { 76 printf("%lld ",d[i]); 77 } 78 printf("%lld",d[V]); 79 return 0; 80 }
總結:
1.分析資料防炸時,不能只看到其中一者而忽略了另一者。
所以選擇建圖的方式很重要。
2.無論dijkstra演算法 還是bellman-ford演算法,使用priority_queue維護都能加快很多執行速度,最差也是跟未使用優先佇列維護複雜度相同。
P1629 郵遞員送信
題目描述
有一個郵遞員要送東西,郵局在節點1。他總共要送n - 1樣東西,其目的地分別是節點2到節點n。
由於這個城市的交通比較繁忙,因此所有的道路都是單行的,共有m條道路。
這個郵遞員每次只能帶一樣東西,並且運送每件物品過後必須返回郵局。
求送完這n - 1樣東西並且最終回到郵局最少需要的時間。
輸入格式
第一行包括兩個整數,n和m,表示城市的節點數量和道路數量。
第二行到第(m + 1)行,每行三個整數,u,v,w表示從u到v有一條通過時間為w的道路。
輸出格式
輸出僅一行,包含一個整數,為最少需要的時間。
輸入輸出樣例
輸入#1 輸出 #1
5 10 83 2 3 5 1 5 5 3 5 6 1 2 8 1 3 8 5 3 4 4 1 8 4 5 3 3 5 6 5 4 2
說明/提示
對於30%的資料,1 ≤ n ≤ 200。
對於100%的資料,1≤n≤1e3,1≤m≤1e5,1≤u,v≤n,1≤w≤1e4,輸入保證任意兩點都能互相到達。
思路一:使用floyd-warshall演算法,計算出任意兩點的值,然後∑d[1][i] + d[i][1]
解析:但這樣顯然有問題,對於本題的資料來說,O(n^3)必超時
0分程式碼:#1~#4WA #5~#10
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <queue> 7 #include <map> 8 #include <set> 9 using namespace std; 10 const int maxn = 1e5 + 5; 11 const int MAXN = 1e3 + 5; 12 const int INF = 1e4 + 5; 13 int V,E,u,v,w,cost[MAXN][MAXN],d[MAXN][MAXN]; 14 long long ans; 15 void init() 16 { 17 for (int i = 1; i <= V; ++i) 18 for (int j = 1; j <= V; ++j) 19 if (i == j) 20 d[i][j] = 0; 21 else 22 d[i][j] = INF; 23 } 24 void warshall_floyd() 25 { 26 for (int k = 1; k <= V; ++k) 27 for (int i = 1; i <= V; ++i) 28 for (int j = 1; j <= V; ++j) 29 d[i][j] = min(d[i][j],d[i][k] + d[k][j]); 30 } 31 int main() 32 { 33 scanf("%d%d",&V,&E); 34 init(); 35 for (int i = 1; i <= E; ++i) 36 { 37 scanf("%d%d%d",&u,&v,&w); 38 d[u][v] = cost[u][v] = w; 39 } 40 warshall_floyd(); 41 ans = 0; 42 for (int i = 2; i <= V; ++i) 43 { 44 ans += d[1][i]; 45 ans += d[i][1]; 46 } 47 return printf("%lld\n",ans),0; 48 }
思路二:使用多次dijkstra演算法,但是具體幾次呢?
分析:題目要求1到i的最短路徑和 以及 i到1的最短路徑和
比如說:1->2->3->5 為1到5的最短路
5->4->2->1 為5到1的最短路:其實路徑長度等於 1->2->4->5 所以我們只需要將原來的邊翻轉,然後在以1為起點求到i的路徑和即為i到1的路徑和
所以最後,我們只需要使用兩次dijkstra演算法就能得到本題的答案,複雜度為O(n^2)。
100分程式碼:#1~#10AC
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <vector> 6 #include <queue> 7 #include <map> 8 #include <set> 9 using namespace std; 10 const int MAXN = 1e3 + 5; 11 const int INF = 10000000; 12 int V,E,u,v,w,ans,cost[MAXN][MAXN],d[MAXN]; 13 bool used[MAXN]; 14 void fill(bool* start, bool* end, bool flag) 15 { 16 for (; start != end; ++start) 17 *start = flag; 18 } 19 void fill(long long* start, long long* end, long long num) 20 { 21 for (; start != end; ++start) 22 *start = num; 23 } 24 void init() 25 { 26 fill(d + 1, d + 1 + V, INF); 27 fill(used + 1, used + 1 + V, false); 28 } 29 void dijkstra(int s) 30 { 31 init(); 32 d[s] = 0; 33 while (true) 34 { 35 int v = -1; 36 for (int u = 1; u <= V; ++u) 37 if (!used[u] && (v == - 1 || d[u] < d[v])) 38 v = u; 39 if (v == -1) 40 break; 41 used[v] = true; 42 for (int u = 1; u <= V; ++u) 43 d[u] = min(d[u],d[v] + cost[v][u]);//cout << "d[" << u << "] = " << d[u] << endl; 44 } 45 } 46 void over() 47 { 48 for (int i = 1; i <= V; ++i) 49 for (int j = i + 1; j <= V; ++j) 50 cost[i][j] ^= cost[j][i] ^= cost[i][j] ^= cost[j][i]; 51 } 52 int main() 53 { 54 scanf("%d%d",&V,&E); 55 for (int i = 1; i <= V; ++i) 56 for (int j = 1; j <= V; ++j) 57 cost[i][j] = INF; 58 for (int i = 1; i <= E; ++i) 59 { 60 scanf("%d%d%d",&u,&v,&w); 61 cost[u][v] = min(cost[u][v],w);//(注意,這個有個坑點,搞的我wa了一發)題目的邊有可能重複輸入 62 } 63 dijkstra(1); 64 for (int i = 2; i <= V; ++i) 65 ans += d[i]; 66 over(); 67 dijkstra(1); 68 for (int i = 2; i <= V; ++i) 69 ans += d[i]; 70 return printf("%d\n",ans),0; 71 }
總結:
1.本題get一個新知識,反向建圖得到 i 到 s的最短路徑
2.本題get一個新坑點,題目雖然未提示可能重邊,但是自己平常考慮wa的時候要注意一下是不是這個問題
3.時間複雜度的分析,100以內n^3沒問題,但1000以外n^3基本必炸