输入样例:
3 3
1 2 -1
2 3 4
3 1 -4
输出样例:
Yes
算法思路:
用spfa判断负环,只需要在spfa求最短路的基础上维护一个cnt数组,cnt[i] = j:表示从源点到顶点i经过的边数是j。
存在负环的条件:cnt[x] >= n,意味着1~x经过了n条边,n+1个点,而图中只有n个点,所以n+1个点中至少有一个点出现了两次,即存在环,要想使得距离更小,环只能是负环。
为了确保图是连通图,不漏下任何一个负环,可以在最开始时构造一个超级源点p,因为点p是虚拟的,dis数组表示p到当前点的距离,初始时dis均为0,cnt数组也均为0,并执行第一次spfa的迭代,将所有图中的点入队。所以判断原图是否有环,就等价于判断新图是否有环。
Java代码:
import java.io.*;
import java.util.*;
public class Main {
static int n, m;
static final int N = 2005, M = 10005;
static int []e = new int[M];
static int []ne = new int[M];
static int []w = new int[M];
static int []h = new int[N];
static int idx = 0;
static int []dis = new int[N];
static boolean []vis = new boolean[N];
static int []cnt = new int[N];
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] split = br.readLine().split(" ");
n = Integer.parseInt(split[0]);
m = Integer.parseInt(split[1]);
Arrays.fill(h, -1);
for(int i = 0; i < m; i++) {
split = br.readLine().split(" ");
int a = Integer.parseInt(split[0]);
int b = Integer.parseInt(split[1]);
int c = Integer.parseInt(split[2]);
add(a, b, c);
}
if(spfa()) System.out.println("Yes");
else System.out.println("No");
}
public static boolean spfa() {
Queue<Integer> qu = new LinkedList<>();
for(int i = 1; i <= n; i++) { // 开始时将所有点都入队,防止图不是连通图
qu.add(i);
vis[i] = true;
}
while(!qu.isEmpty()) {
int t = qu.poll();
vis[t] = false;
for(int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if(dis[j] > dis[t] + w[i]) {
dis[j] = dis[t] + w[i];
cnt[j] = cnt[t] + 1; // 在更新距离的同时更新边数
if(cnt[j] >= n) return true;
if(!vis[j]) {
qu.add(j);
vis[j] = true;
}
}
}
}
return false;
}
public static void add(int a, int b, int c) {
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
}