[关闭]
@morehigh 2017-02-25T03:19:57.000000Z 字数 5659 阅读 1072

CQUPT 集训队专题训练(最短路)

最短路


Dijkstra算法
令G = (V,E)为一个带权有向图,把图中的顶点集合V分成两组,第一组为已求出最短路径的顶点集合S(初始时S中只有源节点,以后每求得一条最短路径,就将它对应的顶点加入到集合S中,直到全部顶点都加入到S中);第二组是未确定最短路径的顶点集合U。在加入过程中,总保持从源节点v到S中各顶点的最短路径长度不大于从源节点v到U中任何顶点的最短路径长度。
SPFA算法
是Bellman-Ford的队列优化,时效性相对好,时间复杂度O(kE)。(k< 与Bellman-ford算法类似,SPFA算法采用一系列的松弛操作以得到从某一个节点出发到达图中其它所有节点的最短路径。所不同的是,SPFA算法通过维护一个队列,使得一个节点的当前最短路径被更新之后没有必要立刻去更新其他的节点,从而大大减少了重复的操作次数。
SPFA算法可以用于存在负数边权的图,这与dijkstra算法是不同的。
与Dijkstra算法与Bellman-ford算法都不同,SPFA的算法时间效率是不稳定的,即它对于不同的图所需要的时间有很大的差别。
在最好情形下,每一个节点都只入队一次,则算法实际上变为广度优先遍历,其时间复杂度仅为O(E)。另一方面,存在这样的例子,使得每一个节点都被入队(V-1)次,此时算法退化为Bellman-ford算法,其时间复杂度为O(VE)。
SPFA算法在负边权图上可以完全取代Bellman-ford算法,另外在稀疏图中也表现良好。但是在非负边权图中,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法,以及它的使用堆优化的版本。通常的SPFA算法在一类网格图中的表现不尽如人意。

A - 最短路 HDU - 2544
题意:

  1. N个地点M条路,起点为1,终点为N,每条路要花费c分钟,求从起点到终点所要花费的最短时间

解题思路:

  1. 求单源最短路径,本弱用的是Dijkstra算法

代码:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #define inf 1e6
  6. using namespace std;
  7. int v[120],d[120],w[120][120];
  8. int n,m;
  9. void dijkstra()
  10. {
  11. memset(v,0,sizeof(v));
  12. for(int i=1;i<=n;i++)
  13. d[i]=inf;
  14. d[1]=0;
  15. for(int i=1;i<=n;i++)
  16. {
  17. int x,m=inf;
  18. for(int j=1;j<=n;j++)
  19. {
  20. if(!v[j]&&d[j]<m)
  21. {
  22. m=d[j];
  23. x=j;
  24. }
  25. }
  26. v[x]=1;
  27. for(int y=1;y<=n;y++)
  28. {
  29. d[y]=min(d[y],d[x]+w[x][y]);
  30. }
  31. }
  32. }
  33. int main()
  34. {
  35. int x,y,wi;
  36. while(scanf("%d%d",&n,&m)!=EOF&&n+m)
  37. {
  38. for(int i=1;i<=n;i++)
  39. for(int j=1;j<=n;j++)
  40. w[i][j]=inf;
  41. for(int i=0;i<m;i++)
  42. {
  43. scanf("%d%d%d",&x,&y,&wi);
  44. w[x][y]=wi;
  45. w[y][x]=wi;
  46. }
  47. dijkstra();
  48. cout<<d[n]<<endl;
  49. }
  50. return 0;
  51. }

B - Wormholes POJ - 3259
题意:

  1. N (1 N 500)块田地,M (1 M 2500)条路,W (1 W 200)个虫洞,两块田地可能有多条路连接,问能不能从某条路出发,通过虫洞回到出发点的时间要比出发时间更早。

解题思路:

  1. spfa算法判断是否有负环存在,将最短路径入队等待松弛,如果松弛超过n次说明有负环存在,用栈和pair来存储路径和路径权值。

代码:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<vector>
  5. #include<queue>
  6. using namespace std;
  7. #define N 505
  8. #define M 2555
  9. #define inf 0x3f3f3f3f
  10. typedef pair<int,int> pii;
  11. #define mp(x,y) make_pair(x,y)
  12. vector<pii>E[N];
  13. bool vis[N];
  14. int cnt[N];
  15. int dis[N];
  16. int n,m1,m2;
  17. void ini()
  18. {
  19. memset(cnt,0,sizeof(cnt));
  20. memset(vis,0,sizeof(vis));
  21. for(int i=1;i<=n;i++)
  22. E[i].clear();
  23. }
  24. bool SPFA(int n)
  25. {
  26. queue<int> que;
  27. while(!que.empty()) que.pop();
  28. for(int i=1;i<=n;i++) {
  29. que.push(i);
  30. vis[i]=true;
  31. dis[i]=inf;
  32. }
  33. while(!que.empty())
  34. {
  35. int u=que.front();
  36. que.pop();
  37. vis[u]=false;
  38. for(int i=0;i<E[u].size();i++)
  39. {
  40. int v=E[u][i].first,cost=E[u][i].second;
  41. if(dis[v]>dis[u]+cost)
  42. {
  43. dis[v]=dis[u]+cost;
  44. if(!vis[v])
  45. {
  46. vis[v]=true;
  47. que.push(v);
  48. if(++cnt[v]>n) return false;
  49. }
  50. }
  51. }
  52. }
  53. return true;
  54. }
  55. int main()
  56. {
  57. int T;
  58. scanf("%d",&T);
  59. while(T--)
  60. {
  61. ini();
  62. scanf("%d%d%d",&n,&m1,&m2);
  63. while(m1--)
  64. {
  65. int u,v,w;
  66. scanf("%d%d%d",&u,&v,&w);
  67. E[u].push_back(mp(v,w));
  68. E[v].push_back(mp(u,w));
  69. }
  70. while(m2--)
  71. {
  72. int u,v,w;
  73. scanf("%d%d%d",&u,&v,&w);
  74. E[u].push_back(mp(v,-1*w));
  75. }
  76. if(SPFA(n)) printf("NO\n");
  77. else printf("YES\n");
  78. }
  79. return 0;
  80. }

C - Stockbroker Grapevine POJ - 1125
题意:

  1. 两个人传递信息需要时间,有n个人传递一条消息,以其中一个人为起点传递此消息,问是否能传递给所有人,并且时间最短。

解题思路:

  1. floyd算法求出以任意一个人为起点,任意一个人为终点的最短时间。然后枚举出每一个人为起点时并把消息传个每个人的时间,求出最短时间。
  2. Floyd的原理是动态规划,设Di,j,k为从ij的只以(1..k)集合中的节点为中间节点的最短路径的长度。

代码:

  1. #include<cstdio>
  2. #include<cstring>
  3. #include<iostream>
  4. #include<vector>
  5. #include<queue>
  6. #define inf 0x3f3f3f
  7. using namespace std;
  8. int n;
  9. int map[200][200];
  10. void floyd()
  11. {
  12. for(int k=1;k<=n;k++)
  13. {
  14. for(int i=1;i<=n;i++)
  15. {
  16. for(int j=1;j<=n;j++)
  17. map[i][j]=min(map[i][k]+map[k][j],map[i][j]);
  18. }
  19. }
  20. int ans=inf,x;
  21. for(int i=1;i<=n;i++)
  22. {
  23. int pa=0;
  24. for(int j=1;j<=n;j++)
  25. {
  26. if(pa<map[i][j])
  27. pa=map[i][j];
  28. }
  29. if(ans>pa)
  30. {
  31. ans=pa;
  32. x=i;
  33. }
  34. }
  35. if(ans!=inf)
  36. cout<<x<<" "<<ans<<endl;
  37. else
  38. cout<<"disjoint"<<endl;
  39. }
  40. int main()
  41. {
  42. int t,x,w;
  43. while(~scanf("%d",&n)&&n)
  44. {
  45. for(int i=1;i<=n;i++)
  46. for(int j=1;j<=n;j++)
  47. {
  48. if(i==j)
  49. map[i][j]=0;
  50. else
  51. map[i][j]=inf;
  52. }
  53. for(int i=1;i<=n;i++)
  54. {
  55. scanf("%d",&t);
  56. while(t--)
  57. {
  58. scanf("%d%d",&x,&w);
  59. map[i][x]=w;
  60. }
  61. }
  62. floyd();
  63. }
  64. return 0;
  65. }

E - 最短路径问题 HDU - 3790
题意:

  1. 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的。

解题思路:

  1. 有两个边权值的最短路径问题,用dijstra算法维护路径最短和花费至最小。由于输入的边可能重复,花费大小可能不一样,所以要先维护出此路径的最小花费。

代码:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <vector>
  6. #include <queue>
  7. #define inf 1000000
  8. using namespace std;
  9. typedef pair<int,int> p;
  10. struct Node
  11. {
  12. int dis,cost;
  13. }edge[1024][1024];
  14. int d[1024],v[1024];
  15. int value[1024];
  16. int n,m;
  17. //vector<int> edge[1024];
  18. void dijkstra(int s)
  19. {
  20. memset(v,0,sizeof(v));
  21. fill(d+1,d+n+1,inf);
  22. d[s]=0;
  23. value[s]=0;
  24. for(int i=1;i<=n;i++)
  25. {
  26. int x,m=inf;
  27. for(int j=1;j<=n;j++)
  28. {
  29. if(!v[j]&&d[j]<m)
  30. {
  31. m=d[j];
  32. x=j;
  33. }
  34. }
  35. v[x]=1;
  36. for(int j=1;j<=n;j++)
  37. {
  38. if(d[j]>d[x]+edge[x][j].dis )
  39. {
  40. d[j]=d[x]+edge[x][j].dis ;
  41. value[j]=value[x]+edge[x][j].cost ;
  42. }
  43. if(d[j]==d[x]+edge[x][j].dis)
  44. {
  45. if(value[j]>value[x]+edge[x][j].cost)
  46. value[j]=value[x]+edge[x][j].cost;
  47. }
  48. }
  49. }
  50. }
  51. int main()
  52. {
  53. int a,b,c,cc;
  54. while(scanf("%d%d",&n,&m)!=EOF&&n+m)
  55. {
  56. for(int i=1;i<=n;i++)
  57. for(int j=1;j<=n;j++)
  58. {
  59. edge[i][j].dis=inf;
  60. edge[i][j].cost=inf;
  61. }
  62. for(int i=0;i<m;i++)
  63. {
  64. scanf("%d%d%d%d",&a,&b,&c,&cc);
  65. if(edge[a][b].dis>c)
  66. {
  67. edge[a][b].dis=edge[b][a].dis=c;
  68. edge[a][b].cost=edge[b][a].cost=cc;
  69. }
  70. if(edge[a][b].dis==c)
  71. {
  72. if(edge[a][b].cost>cc)
  73. edge[a][b].cost=edge[b][a].cost=cc;
  74. }
  75. }
  76. int x,y;
  77. scanf("%d%d",&x,&y);
  78. dijkstra(x);
  79. cout<<d[y]<<" "<<value[y]<<endl;
  80. }
  81. return 0;
  82. }

F - Choose the best route HDU - 2680
题意:

  1. n, m , s,(n<1000,m<20000,1=<s<=n),n代表汽车站的数量,m代表两个相连的车站的数量,s代表Kiki所要到达的终点,w个车站是Kiki的起点,求出Kikiw中的一个车站上车,所花费的时间最少

解题思路:

  1. 由于dijkstra算法求得是单源到各个点的最短路径,所以我们将Kiki所要到达的终点当作起点,求出单源到多个终点的最短距离

代码:

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <algorithm>
  5. #include <vector>
  6. #include <queue>
  7. #define inf 0x3f3f3f
  8. using namespace std;
  9. int w[1024][1024];
  10. int d[1024],v[1024];
  11. int n,m,s;
  12. void dijkstra()
  13. {
  14. memset(v,0,sizeof(v));
  15. for(int i=1;i<=n;i++)d[i]=inf;
  16. d[s]=0;
  17. for(int i=1;i<=n;i++)
  18. {
  19. int x,m=inf;
  20. for(int j=1;j<=n;j++)
  21. {
  22. if(!v[j]&&d[j]<m)
  23. {
  24. x=j;
  25. m=d[j];
  26. }
  27. }
  28. v[x]=1;
  29. for(int j=1;j<=n;j++)
  30. d[j]=min(d[j],d[x]+w[x][j]);
  31. }
  32. }
  33. int main()
  34. {
  35. int p,q,t,x,e;
  36. while(scanf("%d%d%d",&n,&m,&s)!=EOF)
  37. {
  38. for(int i=1;i<=n;i++)
  39. for(int j=1;j<=n;j++)
  40. w[i][j]=inf;
  41. for(int i=0;i<m;i++)
  42. {
  43. scanf("%d%d%d",&p,&q,&t);
  44. if(w[q][p]>t)
  45. w[q][p]=t;
  46. }
  47. dijkstra();
  48. scanf("%d",&x);
  49. int ans=inf;
  50. for(int i=0;i<x;i++)
  51. {
  52. scanf("%d",&e);
  53. ans=min(ans,d[e]);
  54. }
  55. if(ans==inf)
  56. cout<<"-1"<<endl;
  57. else
  58. cout<<ans<<endl;
  59. }
  60. return 0;
  61. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注