P3385 【模板】负环

这篇博客介绍了如何使用SPFA(Shortest Path Faster Algorithm)算法来判断一个有向图中是否存在从顶点1出发能到达的负环。SPFA有BFS和DFS两种实现方式,对于仅判断负环存在的任务,DFS-SPFA更高效。在没有负环的情况下,BFS-SPFA用于求最短路会更快。文章提供了C++代码示例,分别展示了BFS-SPFA和DFS-SPFA的实现,并讨论了两种方法的优劣。
摘要由CSDN通过智能技术生成

P3385 【模板】负环

题意:

给定一个 n 个点的有向图,请求出图中是否存在从顶点 1 出发能到达的负环。

负环的定义是:一条边权之和为负数的回路。

题解:

先说结论:
判断给定的有向图中是否存在负环。
利用 spfa 算法判断负环有两种方法:
1) spfa 的 dfs 形式,判断条件是存在一点在一条路径上出现多次。
2) spfa 的 bfs 形式,判断条件是存在一点入队次数大于总顶点数。
判断负环一般用BF或者spfa
BF是通过不但迭代计算最短路,因为存在负环,则不可能计算出最短路,就会不停的迭代,正常迭代n-1次就结束,若第n次迭代仍然有结点的最短路能被更新,说明图中有负环。复杂度为O(nm)
spfa是队列优化版的BF。SPFA因为使用队列,所以没办法直接知道目前进行第几轮迭代。BF的第i轮迭代实际上就是计算最短路包含i条边的节点。所以我们用cnt[x]表示1到x的最短路包含的边数,cnt[1] = 0,每次用dis[x] + w[x][y]更新dis[y]时,就用cnt[x] + 1来更新cnt[y].如果cnt[y] > =n,说明最短路包含n个边,存在负环
另一个方法是记录每个点的入队次数来判断负环,若有节点入队次数> = n,则存在负环。(这个方法慢)
代码为SPFA的第一种方法

代码:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);
   return s*w;
}
struct edge{
	int v,w;
	edge(int v=0,int w=0):v(v),w(w){};
};
const int maxn=3e3+9;
vector<edge>e[maxn];
int dis[maxn],cnt[maxn],vis[maxn];
int n,m;
bool spfa(int s){
	queue<int>q;
	memset(dis,0x3f,sizeof dis);
	memset(cnt,0,sizeof cnt);
	memset(vis,0,sizeof vis);
	q.push(s);
	vis[s]=1;
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=0;i<e[u].size();i++)
		{
			int v=e[u][i].v;
			int w=e[u][i].w;
			if(dis[u]+w<dis[v])
			{
				dis[v]=dis[u]+w;
				cnt[v]=cnt[u]+1;
				if(cnt[v]>=n)return 1;
				if(vis[v]==0)
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}	
	}
	return 0;
}
int main()
{
	int t;
	cin>>t;
	while(t--){
		
		cin>>n>>m;
		for(int i=1;i<=n;i++)e[i].clear();
		for(int i=1;i<=m;i++)
		{
			int u,v,w;
			cin>>u>>v>>w;
			e[u].push_back(edge(v,w));
			if(w>=0)e[v].push_back(edge(u,w));
		}
		if(spfa(1))cout<<"YES"<<endl;
		else cout<<"NO"<<endl; 
	} 
}

BFS-SPFA 与 DFS-SPFA 的优劣

SPFA 有 BFS 和 DFS 两种实现方式,如果仅仅要判断是否存在负环,DFS-SPFA 要比 BFS-SPFA 快上很多。但是在没有负环时要求出解,DFS-SPFA 会比 BFS-SPFA 慢很多。

BFS:BFS处理环能力较弱,所以该方法若遇到负环有可能TLE

bool spfa(int org){
  memset(dis, 60, sizeof(dis));
  int i;
  queue<int> Q;
  dis[org] = 0, vis[org] = true, Q.push(org);
  while(!Q.empty()){
    int u = Q.front(); Q.pop();
    vis[u] = false;
    for(i = head[u]; i; i = e[i].next){
      int v = e[i].to, w = e[i].w;
      if(dis[v] > dis[u] + w){
        dis[v] = dis[u] + w;
        cnt[v] = cnt[u] + 1;
        if(cnt[v] == N + 1) return false;
        if(!vis[v]) Q.push(v), vis[v] = true;
      }
    }
  }
  return true;
}

DFS:处理最短路能力较若弱,一般针对负环的题目
由于DFS-SPFA可以在找到负环后及时退出,所以不会像BFS-SPFA那样TLE

bool spfa(int u){
  vis[u] = true;
  int i;
  for(i = head[u]; i; i = e[i].next){
    int v = e[i].to, w = e[i].w;
    if(dis[v] > dis[u] + w){
      dis[v] = dis[u] + w;
      if(vis[v]) return false;
      if(!spfa(v)) return false;
    }
  }
  vis[u] = false;
  return true;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值