单源最短路和最长路总结

yuanheci 2023年02月27日 458次浏览

单源最短路和最长路一共可分为:
(1)(1) 加法最短路
(2)(2) 加法最长路
(3)(3) 乘法最短路
(4)(4) 乘法最长路

下面就一一来分析一下~

1、 加法最短路

最基础的情况,用经典的单源最短路算法都可以求解。

2、 加法最长

可以用spfa
当满足图中边权全为负数时,可以用dij

3、 乘法最短路

可以用spfa
当所有边权均满足w[i] >= 1时,可以用dij

4、乘法最长路

可以用spfa
当所有边权均满足0 < w[i] < 1时,可以用dij

小结:关于dij在加法中,经典dij可在全负权边图中跑最长路、全正权边图中跑最短路。


分析方式总结

  套用最短路和最长路原理来分析,SPFA的正确性是基于它的松弛操作,所以自然是可以求乘积最小值和乘积最大值的。
  考虑Dij为什么不一定行,因为Dij的最短路算法是贪心的,它贪心的选最小的并且认为这个点不会被更新了,为什么不会被更新了?因为没有负权边,从而你加入优先队列里的dis只会越来越大,毕竟一个数+一个正数一定变大,不可能以后取出来某一个点更新这个点,故我这个点取出来了,就不会再使用。Dij的正确性是基于 “放入优先队列的点的dis是单调的” 这一条件。所以对于求最短路,我们要求dis单调递增,即不存在负权边,对于最大乘积,我们要求乘积只允许单调变小,即乘的在(0,1)之间,对于最小乘积,我们要求乘积必须单调变大,即所有边权都是>=1的。(似乎也不用严格单调,只要不增不减就行了)。

另外注意如果用Dij求最长路(加法/乘法),那么记得要用大根堆。


例题

最小花费

朴素Dij做法

  1. #include <iostream>
  2. #include <algorithm>
  3. #include <cstring>
  4. using namespace std;

  5. const int N = 2010;
  6. int n, m;
  7. double w[N][N];
  8. double dist[N]; //0表示不相连
  9. int S, T;
  10. bool st[N];

  11. void dijkstra(){
  12. dist[S] = 1; //乘法的零元是1
  13. for(int i = 1; i < n; i++){
  14. int t = -1;
  15. for(int j = 1; j <= n; j++){
  16. if(!st[j] && (t == -1 || dist[j] > dist[t]))
  17. t = j;
  18. }
  19. st[t] = true;
  20. for(int k = 1; k <= n; k++)
  21. if(dist[k] < dist[t] * w[t][k])
  22. dist[k] = dist[t] * w[t][k];
  23. }
  24. }

  25. int main(){
  26. scanf("%d%d", &n, &m);

  27. for(int i = 1; i <= m; i++){
  28. int x, y, z;
  29. scanf("%d%d%d", &x, &y, &z);
  30. w[x][y] = w[y][x] = (100 - z) / 100.0;
  31. }
  32. scanf("%d%d", &S, &T);

  33. dijkstra();
  34. printf("%.8f\n", 100 / dist[T]);

  35. return 0;
  36. }

堆优化版本dij

  1. #include <bits/stdc++.h>
  2. using namespace std;

  3. #define x first
  4. #define y second
  5. typedef pair<double, int> PDI;
  6. const int N = 2010, M = 200010;
  7. int h[N], e[M], ne[M], idx;
  8. double w[M], dist[N];
  9. bool st[N];
  10. int n, m;
  11. int start, ed;

  12. void add(int a, int b, double c){
  13. e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
  14. }

  15. void dijkstra(){
  16. priority_queue<PDI, vector<PDI>> heap; //大根堆
  17. dist[start] = 1;
  18. heap.push({1, start}); //乘法的零元是1
  19. while(heap.size()){
  20. auto t = heap.top();
  21. heap.pop();
  22. int ver = t.y;
  23. double dis = t.x;
  24. if(st[ver]) continue;
  25. st[ver] = true;
  26. for(int i = h[ver]; ~i; i = ne[i]){
  27. int j = e[i];
  28. if(dist[j] < dis * w[i]){
  29. dist[j] = dis * w[i];
  30. heap.push({dist[j], j});
  31. }
  32. }
  33. }
  34. }

  35. void solve(){
  36. scanf("%d%d", &n, &m);
  37. memset(h, -1, sizeof h);
  38. while(m -- ){
  39. int a, b, c;
  40. scanf("%d%d%d", &a, &b, &c);
  41. add(a, b, (100 - c) / 100.0), add(b, a, (100 - c) / 100.0);
  42. }
  43. scanf("%d%d", &start, &ed);
  44. dijkstra();
  45. printf("%.8lf\n", 100.0 / dist[ed]);
  46. }

  47. int main(){
  48. int T = 1;
  49. // scanf("%d", &T);
  50. while(T -- ){
  51. solve();
  52. }

  53. return 0;
  54. }

spfa做法

  1. #include <bits/stdc++.h>
  2. using namespace std;

  3. const int N = 2010, M = 200010;
  4. int h[N], e[M], ne[M], idx;
  5. double w[M];
  6. double dist[N];
  7. bool st[N];
  8. int q[N], hh, tt;
  9. int n, m, start, ed;

  10. void add(int a, int b, double c){
  11. e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
  12. }

  13. void spfa(){
  14. dist[start] = 1; //乘法的零元是1
  15. st[start] = true;
  16. hh = 0, tt = 0;
  17. q[tt++] = start;
  18. while(hh != tt){
  19. int t = q[hh++];
  20. if(hh == N) hh = 0;
  21. st[t] = false;
  22. for(int i = h[t]; ~i; i = ne[i]){
  23. int j = e[i];
  24. if(dist[j] < dist[t] * w[i]){
  25. dist[j] = dist[t] * w[i];
  26. if(!st[j]){
  27. st[j] = true;
  28. q[tt++] = j;
  29. if(tt == N) tt = 0;
  30. }
  31. }
  32. }
  33. }
  34. }

  35. int main(){
  36. scanf("%d%d", &n, &m);
  37. memset(h, -1, sizeof h);
  38. for(int i = 0; i < m; i++){
  39. int a, b, c;
  40. scanf("%d%d%d", &a, &b, &c);
  41. add(a, b, 1.0 - 1.0 * c / 100);
  42. add(b, a, 1.0 - 1.0 * c / 100);
  43. }
  44. scanf("%d%d", &start, &ed);
  45. spfa();
  46. printf("%.8lf\n", 100.0 / dist[ed]);

  47. return 0;
  48. }