1. 程式人生 > 其它 >圖論_最短路例題(更新中)

圖論_最短路例題(更新中)

P4779 【模板】單源最短路徑(標準版)

題目描述

給定一個n個點,m條有向邊的帶非負權圖,請你計算從s出發,到每個點的距離。

資料保證你能從s出發到任意點。

輸入格式

第一行為三個正整數n, m, s。 第二行起m行,每行三個非負整數ui, vi, wi,表示從uivi有一條權值為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

說明/提示

1n1e5;

1m2e5;

s = 1;

1ui,vin;

0wi1e9,

0wi1e9。

思路: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樣東西並且最終回到郵局最少需要的時間。

輸入格式

第一行包括兩個整數,nm,表示城市的節點數量和道路數量。

第二行到第(m + 1)行,每行三個整數,u,v,w表示從uv有一條通過時間為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%的資料,1n1e31m1e51u,vn,1w1e4,輸入保證任意兩點都能互相到達。

思路一:使用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基本必炸