负环详解
什么是负环?
顾名思义,就是一个所有边的边权和为负数的环
出现负环会怎么样?
我们知道,一般情况下,图上的最短路都是确定的。但是一旦图上有一个负环,\(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了,有些生疏,也就不放例题了(其实是没时间找了)。
不知我的博客是否对你有帮助呢?如果对你有些许帮助,不妨点个推荐吧。