今天就来理一理上一篇博客提到的用SPFA快速判负环的做法
先来回顾一下bfs版(就是用队列进行操作的)是如何判断负环的
当一个点被松弛(n - 1)次后,还能再松弛,说明这个图存在负环
那么从这个描述中我们就可以很轻易的发现其效率的低下,因为如果要让一个点被松弛 n 次,那就相当于O (n * m )的复杂度,就是一开始的Bellman—Ford做法了,显然不可行
换一个思路,如果我们用dfs去进行操作,对于每一个被松弛的点,马上又从它开始往下松弛,(而不是将它放在队列中,等它前面的都出队后,再操作),这样下来,如果当前这个点 u 可以松弛的点 v 是被访问过的,那么就说明一定存在负环。
相当于,如果你走了一圈走到开头,发现还能松弛起点,那么这个圈的权值和就一定是负数,否则你是不可能松弛到起点的
这样的效率就大大提高了
但不排除毒瘤的出题人(比如洛谷上的3385,这个数据绝对是来搞笑的),专门出数据卡 dfs ,这时候你还是乖乖用 bfs 比较好。
但凭心而论,还是dfs快
代码
(例题见洛谷1993,或BZOJ3436,这道题也要用到差分约束)
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<queue>
#define N 30009
using namespace std;
int n,m,tot=0;
int nxt[N],head[N],to[N],w[N],dis[N];
void add(int x,int y,int z){
nxt[++tot]=head[x];
head[x]=tot;
to[tot]=y;
w[tot]=z;
}
int cnt[N];
bool vst[N],hh=0;
queue<int > q;
int spfa(int x){
vst[x]=1;
int i,j,t=1;
for(i=head[x];i;i=nxt[i]){
if(t==0) return 0;//小小的优化
int y=to[i];
if(dis[y]>dis[x]+w[i]){
dis[y]=dis[x]+w[i];
if(vst[y]) return 0;//如果x松弛的点出现过,就可以确定有负环
t=spfa(y);
}
}
vst[x]=0;//回溯的时候别忘了标记清空
return t;
}
int main(){
scanf("%d%d",&n,&m);
int i,j,k,a,b,c;
for(i=1;i<=m;++i){
int x;
scanf("%d",&x);
if(x==1){
scanf("%d%d%d",&a,&b,&c);
add(a,b,-c);
}
if(x==2){
scanf("%d%d%d",&a,&b,&c);
add(b,a,c);
}
if(x==3){
scanf("%d%d",&a,&b);
add(a,b,0);
add(b,a,0);
}
}
for(i=1;i<=n;++i)
{
dis[i]=0x3f3f3f3f;
vst[i]=0;
}
for(i=1;i<=n;++i)//防止图不连通的情况出现,加一个虚拟点,使得整个图在一次spfa时就能走完
add(0,i,0);
dis[0]=0;
if(spfa(0)) printf("Yes");
else printf("No");
return 0;
}