题目来源:AcWing 852. spfa判断负环
一、题目描述
给定一个 n n n 个点 m m m 条边的有向图,图中可能存在重边和自环, 边权可能为负数。
请你判断图中是否存在负权回路。
输入格式
第一行包含整数
n
n
n 和
m
m
m。
接下来 m m m 行每行包含三个整数 x , y , z x,y,z x,y,z,表示存在一条从点 x x x 到点 y y y 的有向边,边长为 z z z。
输出格式
如果图中存在负权回路,则输出 Yes
,否则输出 No
。
数据范围
1
≤
n
≤
2000
,
1≤n≤2000,
1≤n≤2000,
1
≤
m
≤
10000
,
1≤m≤10000,
1≤m≤10000,
图中涉及边长绝对值均不超过
10000
10000
10000。
输入样例:
3 3
1 2 -1
2 3 4
3 1 -4
输出样例:
Yes
二、SPFA判断负环
我们通过对SPFA算法的学习,知道了SPFA算法就是Bellman-Ford算法的改进。SPFA中 d i s t [ i ] dist[i] dist[i] 数组就是代表源点到结点 i i i 的最短距离。想要利用SPFA算法来判断环的话,就需要额外维护一个数组 c n t cnt cnt 来记录当前这个最短路所走过的边的数量。
在更新 d i s t [ j ] = d i s t [ t ] + w [ i ] dist[j] = dist[t] + w[i] dist[j]=dist[t]+w[i] 时,将 c n t cnt cnt 同时更新: c n t [ j ] = c n t [ t ] + 1 cnt[j] = cnt[t] + 1 cnt[j]=cnt[t]+1,表示从源点到 t t t 结点最短路所经过的边数 + t t t 结点到 j j j 结点的这一条边。当 c n t [ j ] ≥ n cnt[j] ≥ n cnt[j]≥n 时,说明从源点到结点 j j j 经过了 n n n 条边,也就是经过了 n + 1 n+1 n+1 个结点。通过容斥原理必定存在至少两个点相同,一定存在负环。
注意:
- 因为我们求的不是最短路距离的绝对值,因此使用spfa判断负环时, d i s t dist dist 数组不需要初始化。
- 因为我们要求的是图中是否存在负环,但是源点不一定能到达负环所处的位置,因此最初必须将所有的结点入队。
- 因为负环链有可能不大,因此使用栈来代替队列可以加速求解负环,因为不是负环的结点很快就会出栈,但是负环内的结点会频繁的入栈出栈。
三、代码
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e3 + 10, M = 1e4 + 10;
int h[N], ne[M], e[M], w[M], idx;
int dist[N], cnt[N];
bool st[N];
int stk[N]; // 求负环时,栈比队列更好使
int n, m;
void add(int a, int b, int c)
{
w[idx] = c, e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool spfa()
{
// 因为我们这里求的不是距离的绝对值,因此不需要初始化
int tt = 0;
for (int i = 1; i <= n; i++) stk[++tt] = i, st[i] = true;
while (tt)
{
int t = stk[tt--];
st[t] = false;
for (int i = h[t]; ~i; 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])
{
stk[++tt] = 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;
}