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