AcWing 852. spfa判断负环
题目描述
给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你判断图中是否存在负权回路。
输入格式
第一行包含整数 nn 和 mm。
接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。
输出格式
如果图中存在负权回路,则输出
Yes
,否则输出No
。数据范围
1≤n≤20001≤n≤2000,
1≤m≤100001≤m≤10000,
图中涉及边长绝对值均不超过 1000010000。输入样例:
3 3 1 2 -1 2 3 4 3 1 -4
输出样例:
Yes
spfa算法求解
分析
spfa就是队列优化的bellman_ford算法
使用spfa判断图中是否存在负环的话,有两种方法
- 判断一个点是不是已经进入队列了n次,由bellman_ford算法可以知道,如果不存在负环最多经过n次迭代就可以得到1到任何一个点的最短距离,一个点最多被更新n-1次
- 判断到当前点的最短路径长度是不是大于等于n了!如果是的话,就说明存在一条路径有n的长度,那么该路径有n+1个点,必有两个点相同,所以一定存在负环
需要注意的是判断整个图存不存在负环,并不是判断从1开始存不存在负环,所以一开始要把所有点加入到队列中
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int N = 2010;
const int INF = 0x3f3f3f3f;
int n, m;
bool st[N];
int dist[N];
int cnt[N];
struct VER
{
int to;
int w;
};
vector<VER> h[N];
void add(int a, int b, int w)
{
VER ver;
ver.to = b;
ver.w = w;
h[a].push_back(ver);
}
bool spfa()
{
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
queue<int> q;
// 所有点都入队,因为判断的是整个图有没有负环,不是判断从1开始有没有负环,所以都要加入队列
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;
// 到t点的路径长度大于等于n了,说明这条路上有n+1个点
// 由抽屉原理,一定两个点相同,所以一定存在负环
if(cnt[t] >= n) return true;
for(int i = 0; i < h[t].size(); i++)
{
int j = h[t][i].to, w = h[t][i].w;
if(dist[j] > dist[t] + w)
{
dist[j] = dist[t] + w;
cnt[j] = cnt[t] + 1; // t->j路径长度+1
if(!st[j]) // j不在队列中
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
while(m--)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
add(a, b, w);
}
bool t = spfa();
if(t) puts("Yes");
else puts("No");
return 0;
}
时间复杂度
参考文章
https://www.acwing.com/solution/content/6336/
使用spfa算法解决是否存在负环问题
求负环的常用方法,基于SPFA,一般都用方法 2(该题也是用方法 2):
- 方法 1:统计每个点入队的次数,如果某个点入队n次,则说明存在负环
- 方法 2:统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于n,则也说明存在环
1、dist[x] 记录虚拟源点到x的最短距离
2、cnt[x] 记录当前x点到虚拟源点最短路的边数,初始每个点到虚拟源点的距离为0,只要他能再走n步,即cnt[x] >= n,则表示该图中一定存在负环,由于从虚拟源点到x至少经过n条边时,则说明图中至少有n + 1个点,表示一定有点是重复使用
3、若dist[j] > dist[t] + w[i],则表示从t点走到j点能够让权值变少,因此进行对该点j进行更新,并且对应cnt[j] = cnt[t] + 1,往前走一步