题目地址:
https://www.acwing.com/problem/content/854/
给定一个 n n n个点 m m m条边的有向图,图中可能存在重边和自环, 边权可能为负数。请你判断图中是否存在负权回路。
输入格式:
第一行包含整数
n
n
n和
m
m
m。接下来
m
m
m行每行包含三个整数
x
x
x,
y
y
y,
z
z
z,表示存在一条从点
x
x
x到点
y
y
y的有向边,边长为
z
z
z。
输出格式:
如果图中存在负权回路,则输出“Yes”,否则输出“No”。
数据范围:
1
≤
n
≤
2000
1\le n\le 2000
1≤n≤2000
1
≤
m
≤
10000
1\le m\le 10000
1≤m≤10000
0
≤
∣
e
∣
≤
10000
0\le |e|\le 10000
0≤∣e∣≤10000
e
e
e是边长
SPFA算法可以找负环,基本思路是在更新某个点的最短路的时候(这里”某个点的最短路“并没有说源点是谁,其实我们考虑的是每个点都要作为源点去尝试一下。也可以考虑成有个假想的超级源点,这个源点与每个点的距离都是 0 0 0,那么第一次是将超级源点入队,第二次就会将所有顶点入队了),看看到达该点的最短路的边数是否多于 n n n。这里的距离数组dist是不用初始化的,一开始就是 0 0 0,表示自己到自己的最短路是 0 0 0(或者表示这些点到超级源点的距离是 0 0 0)。如果某个点的最短路径边数大于等于 n n n,代表存在一条有重复顶点的路径到达它比那个重复顶点不走,路程反而更短,这就说明存在负环。SPFA找负环一般要把队列换成栈,这样更快。代码如下:
#include <cstring>
#include <iostream>
#include <stack>
using namespace std;
const int N = 100010;
int n, m;
int h[N], w[N], e[N], ne[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() {
stack<int> stk;
for (int i = 1; i <= n; i++) {
stk.push(i);
st[i] = true;
}
while (stk.size()) {
int t = stk.top();
stk.pop();
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.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
spfa() ? puts("Yes") : puts("No");
}
时间复杂度 O ( m n ) O(mn) O(mn),空间 O ( n + m ) O(n+m) O(n+m)。