图论_最短路例题(更新中)

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

[补充] 经过dalao的提醒,我发现并不是edge结构体的问题,而是我对d[u] = min(d[u],d[v] + cost[v][u])的理解不够;

本题即使用edge也可以O(1)查询,不用再自己编写一遍查询循环

1 for (int i = 1; i <= E; ++i)
2 {
3     if (es[i].from == v)             
4             d[e[i].to] = min(d[e[i].to], d[e[i].from] + e[i].cost);
5 }        

优化:有一说一,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基本必炸

 

posted @ 2021-07-22 20:13  QingHUIWuHan  阅读(170)  评论(0)    收藏  举报