负环详解

什么是负环?

顾名思义,就是一个所有边的边权和为负数的环

出现负环会怎么样?

我们知道,一般情况下,图上的最短路都是确定的。但是一旦图上有一个负环,\(s\)\(t\)的最短路就会不远千里的去覆盖上这个环(只要能够到达),并且不厌其烦的走上一遍又一遍。由于负环的边权和是负的,并且它是一个环,也就是说走一遍和走无数遍都停留在进入的那个点。那么最短路每经过一次这个负环,这个费用都会缩小一点,如果经过了无数次,也就是无穷小,也就是不存在最短路。当然这里有一个限定,就是每个点经过的次数不能超过\(1\)次。

既然负环的影响这么大,那么就要引出我们的下一个问题了。

怎么判定负环?

相信大家都学过至少两种最短路的计算方法,而且一定也都会\(dijkstra\)\(SPFA\)这两种算法(如果不会的请先去学习SPFA再来观看此博客)。花开两朵,各表一枝。我们这里只谈\(SPFA\)。大家都知道,\(SPFA\)的判定方式就是不断地收紧每个点到起点的最短路径,每次都不一定会收到最紧,但只要有解,最终一定会收成最紧(这也正是它这么好卡的原因,一点一点的,能不慢吗)。我们前面提到过,如果存在负环,那么最短路会不断缩小至无穷小。那么这里我们就可以应用它的这一特点。在\(SPFA\)中,每个点最短被其他\(n-1\)个点各收紧一次,如果被收紧了\(n\)次,显然是不合理的。那么我们就记录每个点被收紧的次数,有任何点超过\(n\)次,就可以判定存在负环了,如果\(SPFA\)成功运行完了,就证明不存在负环。

奇妙的是,我使用单调队列优化\(SPFA\)没有过这道题,所以大家还是用普通的\(SPFA\)来判定吧(毕竟出题人总不可能让你没法判定)。

题目链接

下面是代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#include<queue>
#define ll long long
#define gc getchar
#define maxn 2050
#define maxm 3050
using namespace std;

inline ll read(){
	ll a=0;;int f=0;char p=gc();
	while(!isdigit(p)){f|=p=='-';p=gc();}
	while(isdigit(p)){a=(a<<3)+(a<<1)+(p^48);p=gc();}
	return f?-a:a;
}int t,n,m;

struct ahaha{
	int w,to,next;
}e[maxm<<1];int tot,head[maxn];
inline void add(int u,int v,int w){
	e[tot]={w,v,head[u]};head[u]=tot++;
}

queue<int>q;
int d[maxn],s[maxn];
inline bool spfa(){d[1]=0;
	q.push(1);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u];~i;i=e[i].next){
			int v=e[i].to;if(d[v]<=d[u]+e[i].w)continue;
			if(s[v]+1==n)return 1;++s[v];d[v]=d[u]+e[i].w;
			q.push(v);
		}
	}return 0;
}

inline void clear(){tot=0;
	memset(head,-1,sizeof head);
	memset(d,63,sizeof d);
	memset(s,0,sizeof s);
}

int main(){
	t=read();
	while(t--){clear();
		n=read();m=read();
		for(int i=1;i<=m;++i){
			int u=read(),v=read(),w=read();
			add(u,v,w);if(w>=0)add(v,u,w);
		}
		puts(spfa()?"YE5":"N0");
		while(!q.empty())q.pop();
	}
	return 0;
}

很久没有接触过OI了,有些生疏,也就不放例题了(其实是没时间找了)。

不知我的博客是否对你有帮助呢?如果对你有些许帮助,不妨点个推荐吧。

posted @ 2019-08-16 08:25  子谦。  阅读(2728)  评论(0编辑  收藏  举报
Live2D
//雪