负环

创建日期:2025-01-12


负环是什么

负环的定义:

一张图中一条边权之和为负数的回路。

通常,我们使用已经死去的SPFA来判定是否负环。

判定负环的两种方法

1.从边出发:统计每个点最短路经过的边的数量

易知,正常情况下,在一条最短路上最多有 \(n - 1\) 条边,故可以使用 \(cnt\) 数组统计每个点的最短路经过了多少边,当有 \(cnt_i \ge n\) 时,\(i\) 的最短路上一定有负环!

\(cnt\) 怎么统计呢?很简单,只需要在 \(dis_v > dis_u + w\),即要更新某个点的 \(dis\) 值时,顺便加上对 \(cnt_v\) 的更新和 \(cnt_v\) 是否大于等于 \(n\) 的特判。

模板代码如下:

bool SPFA() {
	memset(dis, 0x3f, sizeof(dis));
	dis[1] = 0, q.push(1);
	
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inq[u] = false;
		
		for (int i = 0; i < e[u].size(); i++) {
			int v = e[u][i].v, w = e[u][i].w;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				cnt[v] = cnt[u] + 1;
				if (cnt[v] >= n)
					return true;
				if (!inq[v])
					q.push(v), inq[v] = true;
			}
		}
	}
	return false;
}

2.从点出发:统计每个点被其他点松弛的次数

可以发现,在一张图没有负环的情况下,每个点最多被除自己外的每个点松弛一次,故使用 \(cnt\) 数组统计每个点被其他不相同的点松弛了几次,当有 \(cnt_i \ge n\) 时,图中一定存在负环!

\(cnt\) 数组应该怎么求呢?

(1).在出队时统计

当一个点出队时,将 \(cnt_u\)++。

这里给出此做法的代码:

bool SPFA() {
	memset(dis, 0x3f, sizeof(dis));
	dis[1] = 0, q.push(1);
	
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		inq[u] = false;
		
		if (++cnt[u] >= n)
			return true;
		
		for (int i = 0; i < e[u].size(); i++) {
			int v = e[u][i].v, w = e[u][i].w;
			if (dis[v] > dis[u] + w) {
				dis[v] = dis[u] + w;
				if (!inq[v])
					q.push(v), inq[v] = true;
			}
		}
	}
	return false;
}

这种做法显然是没有问题的,但有很多人容易错在下一种求法。

(2).在更新 \(dis\) 时更新 \(cnt\)

先给出局部代码:

if (dis[v] > dis[u] + w) {
	dis[v] = dis[u] + w;
	cnt[v]++;
	if (cnt[v] >= n)
		return true;
	if (!inq[v])
		q.push(v), inq[v] = true;
}

在没有重边的情况下,这种求法固然是对的。但当有重边时,这种写法是错误的!

如以下例子:

按照上述做法,若依次按100,99,98的顺序建图,则 \(u\) 一个点就将 \(cnt_v\) 增加了3次,影响了负环的判定。

综上所述,还是建议用从边出发统计 \(cnt\) 的做法,以防出现不必要的错误。

负环与二分结合的例题

UVA11090
洛谷P2868

posted @ 2026-05-19 11:49  xubaichuan  阅读(13)  评论(0)    收藏  举报