图论_最短路例题(更新中)
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基本必炸

浙公网安备 33010602011771号