0722-DFS版SPFA-小K的农场

今天就来理一理上一篇博客提到的用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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值