一般情况下,我们用第二种。
在第一种中,如果有一个点入队n次,代表这个点被更新了n次。
负环: 一个有向图/无向图中 环路的边权和<0
因为是一个环,所以可以循环无限次,那么这些环上的点的距离就会变成-∞
求负环:
基于spfa
spfa 每入队一次 就相当于更新一次 如果入队>=n次
在bellman_ford中 每更新一次 最短距离变小 但一个点的最短距离不可能变小n次
1 统计每个点入队的次数 如果某个点入队n次
说明存在负环
←o
↓ ↑
o→o→o…o→o 总共n个点,则对于点i到其他点最多n-1条边,入队n次说明包含n条让dist[i]变小的边
同时因为更新原则是加上第n条边后最短路权重变小
所以第n条边是负的 则该路径一定存在负环
2 统计当前每个点的最短路中所包含的边数,如果某个点的最短路所包含的边数>=n
说明存在负环
n条边 则一定有n+1个点 但我们总共就n个点 所以这条最短路上一定有环
同时因为更新原则是加上第n条边后最短路权重变小
所以第n条边是负的 则该路径一定存在负环
推荐第2种方法:
考虑:
当数据如 -1
o←o
-1↓ ↑-1
o→o
-1
如果用第一种方法 转完一圈之后每个点只入队一次,达到判定要求则需要转n圈
O(n^2)
如果用第二种方法 转完一圈之后就能达到判定要求
O(n)
还有一个问题:
负环不一定从起点走到
4
↓ ↑
1→…2→3
解决方案:
将所有点入队的同时把所有点的距离初始化为0
q.push(node) for all_node
dist[node] = 0 for all_node
why 所有点入队? (结合虚拟源点建新图理解)
1 虚拟源点0向所有点连一条长度是0的边构成一条新的图
同时以虚拟源点0作为新图的起点
2 原图中存在负环 == 新图中存在负环
而新图里所有的负环一定能从虚拟源点出发走到
3 那么我们对新图做spfa时就是把虚拟源点0加入queue
而0 pop出来后队列会把所有原图的节点加入queue
why dist[node]=0?
1 有负环 == 做完spfa后 存在点i dist[i] = -∞
2 赋的初值0也是有限值,做完spfa后都会变成-∞
3 w[node]都是有限值 则必然要更新无限次(更新次数>=n)
最后来个玄学操作
spfa O(m) ~ O(nm)
当spfa效率比较低的时候(一直结束不了的时候)
等价于 存在负环
可测量化:当所有点入队次数超过2n,我们就认为图中很大可能存在负环
作者:仅存老实人
链接:https://www.acwing.com/solution/content/20506/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
trick做法,老老实实地求有可能超时。
//这道题也有收藏题解,利用抽屉原理,判断一下最短路径上是否有超过n-1
//条边,cnt[i]存i号点到源点的最短路上,边的数量。
//这道题让判断整个图中有无负环、上一题是求1到n的最短路,这道题也并不是让求从1开始的负环
//所以题解中有个思想很好,就是假设一个虚拟原点,初始把所有的点都加到队列中
/*
多加一个0号顶点,到其他顶点的距离都是零,求0到其他顶点的最短路,如果0到i号顶点的最短路中超过了n-1个节点
那么整个图中必定存在负环。那么本题中就必定存在负环,所以说开始把所有顶点都加入到队列中的操作,等于上述设虚拟原点
的操作,上述虚拟原点的新图中,0到任意一个点有负环,就等于原来的图中一定存在负环,可以画个图理解一下
*/
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=100010;
int n,m;
int h[N],w[N],ne[N],e[N],idx;
int dist[N], cnt[N];
bool st[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
bool spfa()
{
//这里的把dist数组初始化为正无穷的操作也就不用了
//因为我们最后注意的是cnt数组,而不是dist,dist刚开始是0的话也无所依
//图中存在负环的话,cnt必然会>=n,相当于所有距离都减去了正无穷,但是并不
//影响最后cnt的判断
//更牛逼的解释来了!
// 观察这个更新操作,if(dist[j] > dist[t] + w[i]) )
//如果存在负环,则一定会更新无穷次。cnt数组肯定会>=n的 !所以不初始化dist也没事!!
queue<int> q;
for(int i=1;i<=n;i++)
{
st[i]=true;
q.push(i);
}
while(q.size())
{
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i])
{
int j=e[i];
if(dist[j]>dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n) return true;
if(!st[j])
{
q.push(j);
st[j]=true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}