AcWing 852. spfa判断负环

题目传送门

题目描述

给定一个 nn 个点 mm 条边的有向图,图中可能存在重边和自环, 边权可能为负数

请你判断图中是否存在负权回路。

输入格式

第一行包含整数 nn 和 mm。

接下来 mm 行每行包含三个整数 x,y,zx,y,z,表示存在一条从点 xx 到点 yy 的有向边,边长为 zz。

输出格式

如果图中存在负权回路,则输出 Yes,否则输出 No

数据范围

1≤n≤20001≤n≤2000,
1≤m≤100001≤m≤10000,
图中涉及边长绝对值均不超过 1000010000。

输入样例:

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

输出样例:

Yes

spfa算法求解

分析

spfa就是队列优化的bellman_ford算法

使用spfa判断图中是否存在负环的话,有两种方法

  1. 判断一个点是不是已经进入队列了n次,由bellman_ford算法可以知道,如果不存在负环最多经过n次迭代就可以得到1到任何一个点的最短距离,一个点最多被更新n-1次
  2. 判断到当前点的最短路径长度是不是大于等于n了!如果是的话,就说明存在一条路径有n的长度,那么该路径有n+1个点,必有两个点相同,所以一定存在负环

需要注意的是判断整个图存不存在负环,并不是判断从1开始存不存在负环,所以一开始要把所有点加入到队列中

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector> 
#include<queue>
using namespace std;
const int N = 2010;
const int INF = 0x3f3f3f3f;
int n, m;
bool st[N];
int dist[N];
int cnt[N];
struct VER
{
	int to;
	int w;
};
vector<VER> h[N];

void add(int a, int b, int w)
{
	VER ver;
	ver.to = b;
	ver.w  = w;
	h[a].push_back(ver);
}

bool spfa()
{
	memset(dist, 0x3f, sizeof dist);
	dist[1] = 0;
	
	queue<int> q;
	// 所有点都入队,因为判断的是整个图有没有负环,不是判断从1开始有没有负环,所以都要加入队列 
	for(int i = 1; i <= n; i++)
	{
		st[i] = true;
		q.push(i);
	}
	
	
	while(q.size())
	{
		int t = q.front();
		q.pop();
		
		st[t] = false;
		
		// 到t点的路径长度大于等于n了,说明这条路上有n+1个点
		// 由抽屉原理,一定两个点相同,所以一定存在负环 
		if(cnt[t] >= n) return true;
		
		for(int i = 0; i < h[t].size(); i++)
		{
			int j = h[t][i].to, w = h[t][i].w;
			
			if(dist[j] > dist[t] + w)
			{
				dist[j] = dist[t] + w;
				cnt[j] = cnt[t] + 1; // t->j路径长度+1 
				if(!st[j]) // j不在队列中 
				{
					q.push(j);
					st[j] = true;
				}	
			}	
		} 
	}
	
	return false;
}	

int main()
{
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int a, b, w;
		scanf("%d%d%d", &a, &b, &w);
		add(a, b, w);
	}
	bool t = spfa();
	
	if(t) puts("Yes");
	else  puts("No");
	
	return 0;
}


时间复杂度

参考文章

https://www.acwing.com/solution/content/6336/

使用spfa算法解决是否存在负环问题

求负环的常用方法,基于SPFA,一般都用方法 2(该题也是用方法 2):

  • 方法 1:统计每个点入队的次数,如果某个点入队n次,则说明存在负环
  • 方法 2:统计当前每个点的最短路中所包含的边数,如果某点的最短路所包含的边数大于等于n,则也说明存在环

1、dist[x] 记录虚拟源点到x的最短距离

2、cnt[x] 记录当前x点到虚拟源点最短路的边数,初始每个点到虚拟源点的距离为0,只要他能再走n步,即cnt[x] >= n,则表示该图中一定存在负环,由于从虚拟源点到x至少经过n条边时,则说明图中至少有n + 1个点,表示一定有点是重复使用

3、若dist[j] > dist[t] + w[i],则表示从t点走到j点能够让权值变少,因此进行对该点j进行更新,并且对应cnt[j] = cnt[t] + 1,往前走一步

posted @ 2022-03-02 14:37  VanHope  阅读(48)  评论(0编辑  收藏  举报