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基本必炸