POJ2679 SPFA求负环

本文介绍了一种使用SPFA算法寻找图中是否存在负环的方法,并详细解释了如何通过限制顶点出队次数来检测负环的存在。同时,文章还提供了一个完整的C++实现代码示例,包括特殊的边删除技巧及确保算法正确性的注意事项。
SPFA求负环
注意事项:
	1. 不要让一个点出队n次就算负环,如果只有一个点呢?出队n+1次费不了多少时间。
	2. 从终点往回遍历不到的点即便有负环也无所谓,因为它根本无法在s → t的路径上累加负权值。
	3. 此题注意删边方法。
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#define N 3000
#define M 101000
using namespace std;
//只能走权值最小的边 
struct Syndra
{
	int u,v,w,len,next;
}e[M];
int head[N],cnt;
int dist[N],len[N],visit[N],s,t,n,m;
int mini[N],able[N],num[N];
void add(int u,int v,int w,int len)
{
	cnt++;
	e[cnt].u=u;
	e[cnt].v=v;
	e[cnt].w=w;
	e[cnt].len=len;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void spfa()
{
	int i,j,k,u,v;
	queue<int> q;
	memset(dist,0x3f,sizeof(dist));
	memset(visit,0,sizeof(visit));
	memset(len,0x3f,sizeof(len));
	memset(num,0,sizeof(num));
	q.push(s);
	dist[s]=len[s]=0;
	visit[s]=1;
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		visit[u]=0;
		if(!able[u])continue;
		num[u]++;
		if(num[u]>n)
		{
			printf("UNBOUND\n");
			return;
		}
		for(i=head[u];i+1;i=e[i].next)
		{
			v=e[i].v;
			if(!able[v])continue;
			if(e[i].w>mini[u])continue;
			if(dist[v]>dist[u]+e[i].w)
			{
				dist[v]=dist[u]+e[i].w;
				len[v]=len[u]+e[i].len;
				if(!visit[v])
				{
					visit[v]=1;
					q.push(v);
				}
			}
			else if(dist[v]==dist[u]+e[i].w)
			{
				if(len[v]>len[u]+e[i].len)
				{
					len[v]=len[u]+e[i].len;
					if(!visit[v])
					{
						visit[v]=1;
						q.push(v);
					}
				}
			}
		}
	}
	if(dist[t]==0x3f3f3f3f)
	{
		printf("VOID\n");
		return ;
	}
	else
	{
		printf("%d %d\n",dist[t],len[t]);
		return ;
	}
}
char a,b,c;
void dfs(int x)
{
	int i;
	if(able[x])return ;
	able[x]=1;
	for(i=head[x];i+1;i=e[i].next)
	{
		if(e[i^1].w==mini[e[i].v])dfs(e[i].v);
	}
}
void build()
{
	int i,j,k;
	cnt=-1;
	memset(num,0,sizeof(num));
	memset(able,0,sizeof(able));
	memset(head,-1,sizeof(head));
	memset(mini,0x3f,sizeof(mini));
	for(i=1;i<=m;i++)
	{
		int u,v,w1,l,w2;
		scanf(" %c%d%c%d%c%d%c%d%c%d%c",&a,&u,&a,&v,&a,&w1,&a,&l,&a,&w2,&a);
		add(u,v,w1,l);
		add(v,u,w2,l);
		mini[u]=min(mini[u],w1);
		mini[v]=min(mini[v],w2);
	}
	dfs(t);
}
int main()
{
//	freopen("test.in","r",stdin);
	while(scanf("%d%d%d%d",&n,&m,&s,&t)!=EOF)
	{
		build();
		spfa();	
	}
	return 0;
}


### ❓问题:如果的边权为 0,那么是否会被认为是? ### ✅ 答案:**不会。** --- ## 🔍 解释: ### 1. **什么是?** 在图论中,**(Negative Cycle)** 是指一个**有向**,其所有边的权值之和 **小于 0**。 即: > 如果存在一个 $ C $,满足: > $$ > \sum_{e \in C} w(e) < 0 > $$ > 则称 $ C $ 为。 - 和 = 0 → 不是(称为 **零**) - 和 > 0 → 正 - 和 < 0 → --- ### 2. **为什么零不算?** 因为 SPFA、Bellman-Ford 等算法检测“”的目的是判断:**是否存在可以无限松弛的路径**,从而导致最短路不存在。 - 在 **** 中:每绕一圈,总距离变小 → 可以无限绕圈,不断更新最短路 → 最短路无下界。 - 在 **零** 中:绕一圈后距离不变 → 虽然可以多走几圈,但不会让最短路变得更小 → **最短路仍然存在**。 因此: > ✅ 零不影响单源最短路的存在性,**不被视为**,也不会被 SPFA 检测为“需要报错”的。 --- ### 3. **SPFA 如何判断?** SPFA 常用两种方式检测: #### 方法一:入队次数超过 `n-1` 或 ≥ `n` ```cpp if (cnt[v] >= n) return true; // 存在 ``` 依据是:**任何最短路最多包含 n-1 条边**。如果某个点被松弛了 ≥n 次,说明路径中出现了,且该能让距离继续减小 → 必为。 ⚠️ 注意:这个方法只能检测到 **能持续松弛的**,也就是边权和 < 0 的。 → 所以 **零不会触发此条件**,因为它不能让 `dis[v]` 变得更小,无法引发新的松弛操作。 #### 方法二:DFS + 栈上判断 类似 Tarjan,维护当前搜索栈上的节点。若发现回到栈中的节点,并计算权值和 < 0,则为。 同样地,只有当的总权重严格小于 0 时才判定为。 --- ### 4. **举个例子说明** ```text A <--(0)--> B <--(0)--> C ^ | |______________| (0) ``` 这是一个三元零:A → B → C → A,总权值 = 0。 - 绕一圈回来,时间没变; - John 回到起点时,时间没有提前; - 不满足“回到过去”的条件; - 所以 **不是题目要的“时间倒流”情况**。 而在像 POJ 3259 这类题中,只有出现 **真正的时间倒流(总耗时 < 0)** 才输出 `"YES"`。 --- ## ✅ 总结 | 类型 | 权值和 | 是否为? | 是否被 SPFA 检测? | 是否影响最短路? | |-------|--------|---------------|---------------------|------------------| | 正 | > 0 | 否 | 否 | 否 | | 零 | = 0 | 否 | 否 | 否 | | | < 0 | ✅ 是 | ✅ 是 | ✅ 是(最短路不存在) | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值