• 【图论】负环


    知识点


    负环,顾名思义,就是一张图上的一个边权和为负数的环。

    负权边在最短路或此类问题上,会导致一些在正权图上能正常运行的算法失效。 

     处理负边权,可以采用Flody算法,Bellman-Ford算法,和优化过的Bellman-Ford算法:SPFA

     在竞赛中,大多数题目会卡SPFA算法,但碰到存在负边权的时候,SPFA算法就是正解了。

     SPFA算法核心代码:

    1. queue <int> q;
    2. inline bool SPFA()
    3. {
    4. memset(dis,INF,sizeof(dis));
    5. memset(vis,0,sizeof(vis));
    6. memset(ans,0,sizeof(ans));
    7. //数组清零
    8. while(!q.empty()) q.pop(); //清空队列
    9. q.push(1); //先加入结点,然后记录
    10. dis[1]=0; //记录路径
    11. vis[1]=1; //统计该点已经搜索过
    12. while(!q.empty())
    13. {
    14. int u=q.front(); //取出队首元素
    15. vis[u]=0; //标记该点
    16. q.pop(); //弹出该点
    17. for(int i=head[u];i!=-1;i=edge[i].next) //链式前向星查找每个点的方式
    18. {
    19. int v=edge[i].to;
    20. if(dis[v]>dis[u]+edge[i].w) //松弛操作
    21. {
    22. dis[v]=dis[u]+edge[i].w;
    23. ans[v]=ans[u]+1; //统计已经走过的路径
    24. if(ans[v]>=n) //路径数大于n,存在点被第二次经过
    25. {
    26. return 1;
    27. }
    28. if(!vis[v]) //如果到达的点不在队列里
    29. {
    30. q.push(v); //将下一个点进队
    31. vis[v]=1;
    32. }
    33. }
    34. }
    35. }
    36. return 0; //没有负环
    37. }

     模板题:洛谷 P3385 【模板】负环

    题目描述

    给定一个 n 个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环。

    负环的定义是:一条边权之和为负数的回路。

    输入格式

    本题单测试点有多组测试数据

    输入的第一行是一个整数 T,表示测试数据的组数。对于每组数据的格式如下:

    第一行有两个整数,分别表示图的点数 n 和接下来给出边信息的条数 m。

    接下来 m 行,每行三个整数u,v,w。

    • 若 w ≥ 0,则表示存在一条从 u 至 v 边权为 w 的边,还存在一条从 v 至 u 边权为 w 的边。
    • 若 w < 0,则只表示存在一条从 u 至 v 边权为 w 的边。

    输出格式

    对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES,否则输出 NO

    输入输出样例

    输入 #1

    2
    3 4
    1 2 2
    1 3 4
    2 3 1
    3 1 -3
    3 3
    1 2 3
    2 3 4
    3 1 -8
    

    输出 #1

    NO
    YES
    

    说明/提示

    数据规模与约定

    对于全部的测试点,保证:

    • 1≤n≤2×10^3,1≤m≤3×10^3。
    • 1≤ u , v ≤n,−10^4≤w≤10^4。
    • 1≤T≤10。

    提示

    请注意,m 不是图的边数。


    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. int t,n,m;
    9. const int maxn=1e7+5,INF=0x3f3f3f3f;
    10. struct node{
    11. int to,next,w;
    12. }edge[maxn<<1];
    13. int num=0,dis[maxn],head[maxn],ans[maxn];
    14. bool vis[maxn];
    15. queue <int> q;
    16. inline int read()
    17. {
    18. int x=0,f=1;
    19. char c=getchar();
    20. while (c<'0'||c>'9')
    21. {
    22. if(c=='-') f=-1;
    23. c=getchar();
    24. }
    25. while(c>='0'&&c<='9')
    26. {
    27. x=(x<<3)+(x<<1)+(c^48);
    28. c=getchar();
    29. }
    30. return x*f;
    31. }
    32. inline void add(int u,int v,int w)
    33. {
    34. edge[++num].to=v;
    35. edge[num].w=w;
    36. edge[num].next=head[u];
    37. head[u]=num;
    38. }
    39. inline void bellman_ford()
    40. {
    41. memset(dis,INF,sizeof(dis));
    42. memset(vis,0,sizeof(vis));
    43. memset(ans,0,sizeof(ans));
    44. //数组清零
    45. while(!q.empty()) q.pop(); //清空队列
    46. q.push(1); //先加入结点,然后记录
    47. dis[1]=0; //记录路径
    48. vis[1]=1; //统计该点已经搜索过
    49. while(!q.empty())
    50. {
    51. int u=q.front(); //取出队首元素
    52. vis[u]=0; //标记该点
    53. q.pop();
    54. for(int i=head[u];i!=-1;i=edge[i].next)
    55. {
    56. int v=edge[i].to;
    57. if(dis[v]>dis[u]+edge[i].w) //松弛操作
    58. {
    59. dis[v]=dis[u]+edge[i].w;
    60. ans[v]=ans[u]+1; //统计已经走过的路径
    61. if(ans[v]>=n) //路径数大于n,存在点被第二次经过
    62. {
    63. printf("YES\n");
    64. return;
    65. }
    66. if(!vis[v]) //如果到达的点不在队列里
    67. {
    68. q.push(v); //将下一个点进队
    69. vis[v]=1;
    70. }
    71. }
    72. }
    73. }
    74. printf("NO\n"); //没有负环
    75. }
    76. int main()
    77. {
    78. t=read();
    79. for(int k=1;k<=t;++k)
    80. {
    81. n=read(); m=read();
    82. memset(head,-1,sizeof(head));
    83. for(int i=1;i<=m;++i)
    84. {
    85. int u=read(),v=read(),w=read();
    86. if(w>=0)
    87. {
    88. add(u,v,w);
    89. add(v,u,w);
    90. }
    91. else add(u,v,w);
    92. }
    93. bellman_ford();
    94. }
    95. return 0;
    96. }

    相关练习:

    1 . 洛谷 P2136 拉近距离

    需要注意的细节:

    1. 可以从1开始找,也可以从n开始找,即可以将图遍历两次。

    2. 在遍历的过程中,只要有一次存在负环,就输出“Forever love”。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. int n,m;
    9. const int maxn=1e5+5,INF=0x3f3f3f3f;
    10. struct node{
    11. int to,next,w;
    12. }edge[maxn<<1];
    13. int num=0,head[maxn],dis[maxn],ans[maxn];
    14. bool vis[maxn];
    15. queue <int> q;
    16. inline int read()
    17. {
    18. int x=0,f=1;
    19. char c=getchar();
    20. while(c<'0'||c>'9')
    21. {
    22. if(c=='-') f=-1;
    23. c=getchar();
    24. }
    25. while(c>='0' && c<='9')
    26. {
    27. x=(x<<3)+(x<<1)+(c^48);
    28. c=getchar();
    29. }
    30. return x*f;
    31. }
    32. inline void write(int x)
    33. {
    34. if(x<0) putchar('-'),x=-x;
    35. if(x>9) write(x/10);
    36. putchar(x%10+'0');
    37. }
    38. inline void add(int u,int v,int w)
    39. {
    40. edge[++num].to=v;
    41. edge[num].w=w;
    42. edge[num].next=head[u];
    43. head[u]=num;
    44. }
    45. inline bool SPFA(int x)
    46. {
    47. memset(dis,INF,sizeof(dis));
    48. memset(vis,0,sizeof(vis));
    49. memset(ans,0,sizeof(ans));
    50. while(!q.empty()) q.pop();
    51. q.push(x);
    52. vis[x]=1;
    53. dis[x]=0;
    54. while(!q.empty())
    55. {
    56. int u=q.front();
    57. vis[u]=0;
    58. q.pop();
    59. for(int i=head[u];i!=-1;i=edge[i].next)
    60. {
    61. int v=edge[i].to;
    62. if(dis[v]>dis[u]+edge[i].w)
    63. {
    64. dis[v]=dis[u]+edge[i].w;
    65. ans[v]=ans[u]+1;
    66. if(ans[v]>=n)
    67. {
    68. return 1;
    69. }
    70. if(!vis[v])
    71. {
    72. q.push(v);
    73. vis[v]=1;
    74. }
    75. }
    76. }
    77. }
    78. return 0;
    79. }
    80. int main()
    81. {
    82. n=read(); m=read();
    83. memset(head,-1,sizeof(head));
    84. for(int i=1;i<=m;++i)
    85. {
    86. int s=read(),t=read(),w=read();
    87. add(s,t,-w); //根据题目条件,路程会减小,路程取相反数
    88. }
    89. bool check=SPFA(1);
    90. int road=dis[n];
    91. bool check2=SPFA(n);
    92. //可以从1开始查找,也可以从n开始查找
    93. if(check == 1 || check2 == 1) printf("Forever love"); //只要有一边存在负环
    94. else write(min(dis[1],road));
    95. return 0;
    96. }

  • 相关阅读:
    【华为OD机试真题 python】n进制减法 【2022 Q4 | 200分】
    k8s 使用小记:如何进入 k8s 部署的 pod
    数据结构笔记(王道考研) 第一章:绪论
    手把手教你部署nginx+php —— k8s从入门到高并发系列教程 (一)
    深度学习(PyTorch)——循环神经网络(RNN)基础篇二
    Nodejs之egg基本使用(初始化项目、内置对象、egg路由、egg控制器)
    Linux网络设置
    网络小白入门篇
    ROS+Pytorch的联合使用示例(语义分割)
    java多线程面试相关的一些问题
  • 原文地址:https://blog.csdn.net/gzkeylucky/article/details/125600858