【模板】负环
题目描述
给定一个 n n n 个点的有向图,请求出图中是否存在从顶点 1 1 1 出发能到达的负环。
负环的定义是:一条边权之和为负数的回路。
输入格式
本题单测试点有多组测试数据。
输入的第一行是一个整数 T T T,表示测试数据的组数。对于每组数据的格式如下:
第一行有两个整数,分别表示图的点数 n n n 和接下来给出边信息的条数 m m m。
接下来 m m m 行,每行三个整数 u , v , w u, v, w u,v,w。
- 若 w ≥ 0 w \geq 0 w≥0,则表示存在一条从 u u u 至 v v v 边权为 w w w 的边,还存在一条从 v v v 至 u u u 边权为 w w w 的边。
- 若 w < 0 w < 0 w<0,则只表示存在一条从 u u u 至 v v v 边权为 w w w 的边。
输出格式
对于每组数据,输出一行一个字符串,若所求负环存在,则输出 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
提示
数据规模与约定
对于全部的测试点,保证:
- 1 ≤ n ≤ 2 × 1 0 3 1 \leq n \leq 2 \times 10^3 1≤n≤2×103, 1 ≤ m ≤ 3 × 1 0 3 1 \leq m \leq 3 \times 10^3 1≤m≤3×103。
- 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1≤u,v≤n, − 1 0 4 ≤ w ≤ 1 0 4 -10^4 \leq w \leq 10^4 −104≤w≤104。
- 1 ≤ T ≤ 10 1 \leq T \leq 10 1≤T≤10。
提示
请注意, m m m 不是图的边数。
题解
题意
传送门:link
给你 T T T 个图,每个图有 n n n 个点和 m m m 条由 u u u 到 v v v 边权为 w w w 的边。
现给你这 m m m 条边,求图中是否有负环。
做法
算法分析
根据题的大意我们可以看出,这是一道单源最短路的题目。
单源最短路算法有 dij 和 SPFA,那这道题应该用哪道算法呢?
我们看一下数据范围:
-
1 ≤ n ≤ 2 × 1 0 3 1 \leq n \leq 2 \times 10^3 1≤n≤2×103, 1 ≤ m ≤ 3 × 1 0 3 1 \leq m \leq 3 \times 10^3 1≤m≤3×103。
-
1 ≤ u , v ≤ n 1 \leq u, v \leq n 1≤u,v≤n, − 1 0 4 ≤ w ≤ 1 0 4 -10^4 \leq w \leq 10^4 −104≤w≤104。
-
1 ≤ T ≤ 10 1 \leq T \leq 10 1≤T≤10。
很明显会有负环,这时 dij 就行不通了,我们就需要用 SPFA 了。
算法讲解
SPFA 为能够有效判断负环的一种单源最短路算法,但其算法复杂度比不上 dij,在存在菊花图的情况下,很容易原地爆炸。
但由于能够判断负环的性质,才使得它在同类最短路算法中没有被淘汰(可应用于 Johnson 全源最短路算法中),其主要思想为贪心。
SPFA 的独特性质在于他的一个点能够不止一次进队,这个特点有利也有弊,
利在于它可以通过入队次数来判断负环;
弊在于它容易被卡(关于SPFA,他死了)。
但在此题中它能完美胜任 dij(他不能判负环)。
算法实现
答题思路很简单,先链式前向星存图,再用 SPFA 遍历即可,所以问题在于 SPFA 的整体框架。
收我们需要一个队列,先把
1
1
1 号点存进去,再遍历到与
1
1
1 号点所连接着的点,依次松弛、入队,如果一个点入队次数大于等于
n
n
n 就直接输出 YES
接着 return
;如果队列为空了,但依旧没有输出 YES
就输出 NO
。
Code
源代码 - C++
//注释极少版
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
int t,cnt;
int dis[10001],v[10001];
int head[10001],tim[10001];
struct Node{
int to,next,dis;
}edge[100001];
void add(int t1,int t2,int t3) {//存图
cnt++;
edge[cnt].to=t2;
edge[cnt].dis=t3;
edge[cnt].next=head[t1];
head[t1]=cnt;
}
void SPFA_dui(int n) {
queue<int> q;
memset(dis,0x3f,sizeof(dis));
memset(v,0,sizeof(v));
memset(tim,0,sizeof(tim));
//初始化
dis[1]=0;
v[1]=1;
q.push(1);//加入 1
while (!q.empty()) {
int x=q.front();
q.pop();
v[x]=0;
for (int i=head[x];i;i=edge[i].next) {//链式前向星遍历
int y=edge[i].to;
if (dis[y]>dis[x]+edge[i].dis) {//relax
dis[y]=dis[x]+edge[i].dis;
tim[y]=tim[x]+1;
if (tim[y]>=n) {//有负环
cout<<"YES"<<endl;
return;
}
if (!v[y]) {//入队
v[y]=1;
q.push(y);
}
}
}
}
cout<<"NO"<<endl;
}
int main() {
scanf("%d",&t);
for (int i=1;i<=t;i++) {
memset(head,0,sizeof(head));//有多组数据,记得清空
int n,m;
scanf("%d%d",&n,&m);
for (int j=1;j<=m;j++) {
int t1,t2,t3;
cin>>t1>>t2>>t3;
add(t1,t2,t3);
if (t3>=0) {
add(t2,t1,t3);
}
}
SPFA_dui(n);
cnt=0;
}
}