【最短路问题】 【模板】负环

148 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情

【模板】负环

题目描述

给定一个 nn 个点的有向图,请求出图中是否存在从顶点 11 出发能到达的负环。

负环的定义是:一条边权之和为负数的回路。

输入格式

本题单测试点有多组测试数据

输入的第一行是一个整数 TT,表示测试数据的组数。对于每组数据的格式如下:

第一行有两个整数,分别表示图的点数 nn 和接下来给出边信息的条数 mm

接下来 mm 行,每行三个整数 u,v,wu, v, w

  • w0w \geq 0,则表示存在一条从 uuvv 边权为 ww 的边,还存在一条从 vvuu 边权为 ww 的边。
  • w<0w < 0,则只表示存在一条从 uuvv 边权为 ww 的边。

输出格式

对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES,否则输出 NO

样例 #1

样例输入 #1

2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8

样例输出 #1

NO
YES

提示

数据规模与约定

对于全部的测试点,保证:

  • 1n2×1031 \leq n \leq 2 \times 10^31m3×1031 \leq m \leq 3 \times 10^3
  • 1u,vn1 \leq u, v \leq n104w104-10^4 \leq w \leq 10^4
  • 1T101 \leq T \leq 10

提示

请注意,mm 不是图的边数。

解题思路 SPFA

如果存在负环,那么就会有负边,这样的话有些点就会被一直更新。所以如果一个点被超 NN 个点更新过,那就说明有负环。

或者,可以记录最短路的边数。如果不存在负环,图的最短路是不会反复经过一条边的。

#include <cstdio>
#include <iostream>
#include <queue>
#include <vector>
#include <cstring> 
using namespace std;
int T, n, m, u, v, w;
vector <pair<int, int> > G[2001];
int dis[2001], cnt[2001];
bool in[2001];
void spfa(){
	memset(dis, 0x3f3f, sizeof(dis));
	memset(in, 0, sizeof(in));
	memset(cnt, 0, sizeof(cnt));
	queue <int> q;
	q.push(1);
	dis[1] = 0;
	in[1] = true;
	cnt[1] = 1;
	while (!q.empty()){
		u = q.front();
		q.pop();
		in[u] = false;
		for (int i = 0; i < G[u].size(); i++){
			v = G[u][i].first;
			w = G[u][i].second;
			if (dis[v] > dis[u] + w){
				dis[v] = dis[u] + w;
				if (!in[v]){
					cnt[v]++;
					q.push(v);
					in[v] = true;
					if (cnt[v] >= n){
						printf("YES\n");
						return;
					} 
				}
			}
		}
	}
	printf("NO\n");
}
int main(){
	scanf("%d", &T);
	for (int  t = 0; t < T; t++){
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; i++){
			G[i].clear();
		}
		for (int i = 0; i < m; i++){
			scanf("%d%d%d", &u, &v, &w);
			G[u].push_back(make_pair(v, w));
			if (w >= 0){
				G[v].push_back(make_pair(u, w)); 
			} 
		}
		spfa();
	}
	return 0;
}