负环

什么是负环?

负环的定义是在一个环中,其环上的权值和小于0

怎么判断负环?

根据负环的定义,我们知道在有负环的图中不存在最短路,因为你可以绕着负环一直跑,而你的路径和却会越来越少!

所以如果我们在有负环的图上跑SPFA,会陷入死循环的!

正因为这点,所以可以用SPFA来判断负环。

cnt[x]表示x这个节点入队了几次(也就是x这个节点的最短路被更新了几次),若图中有n个节点,那么每个节点最多n1次入队,当入队次数,即cnt[x]n时,退出循环,并确定图中有负环。

试题

T1:模板题

T2:天路

题解

模板题:

没什么好说的了,直接上代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N = 20010, M = 30010;
int t, n, m, tot, head[N], ver[M << 1], nex[M << 1], edge[M << 1], dis[N], vis[N], cnt[N], flag;
inline void add (int x, int y, int z) {
	ver[++ tot] = y;
	edge[tot] = z;
	nex[tot] = head[x];
	head[x] = tot;
}
inline void spfa () {
	queue <int> q;
	q.push(1);
	vis[1] = 1;
	dis[1] = 0;
	while (!q.empty()) {
		int x = q.front();
		q.pop();
		vis[x] = 0;
		for (int i = head[x]; i; i = nex[i]) {
			int y = ver[i], w = edge[i];
			if (dis[y] > dis[x] + w) {
				dis[y] = dis[x] + w;
				if (!vis[y]) {
					q.push(y);
					vis[y] = 1;
					cnt[y] ++;
					if (cnt[y] >= n) {
						flag = 1;
						return;
					}
				}
			}
		}
	}
}
int main () {
	scanf("%d", &t);
	while (t --) {
		flag = 0;
		memset(head, 0, sizeof(head));
		memset(ver, 0, sizeof(ver));
		memset(nex, 0, sizeof(nex));
		memset(edge, 0, sizeof(edge));
		memset(dis, 0x3f, sizeof(dis));
		memset(vis, 0, sizeof(vis));
		memset(cnt, 0, sizeof(cnt));
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= m; i ++) {
			int a, b, w;
			scanf("%d%d%d", &a, &b, &w);
			if (w >= 0) {
				add(a, b, w);
				add(b, a, w);
			} else
				add(a, b, w);
		}
		spfa();
		if (flag)
			printf("YE5\n");
		else
			printf("N0\n");
	}
	return 0;
}

天路:

这题第一眼看,跟负环有什么关系呢?

先别急,根据题意,我们知道题目要求的是某个环使得ViCi的值最大,设这个最大值,即答案为ans

显然对于图上所有环,ansViCi,把这个式子变形得到ansCiVi0

因此如果我们把图中每条边的边权设置为ansCiVi,应该得到的是一张没有负环的图。

同时我们知道ans是满足单调性的,比如样例1中的答案2.3,所有大于2.3的答案都是可以使得这张图没有负环的。

但是由于ans必须要等于某个环的ViCi,所以可以二分答案,找到满足条件(不存在负环)且最小的答案。

但是对于这题有几点需要注意:

  • BFS版的SPFA会超时,需要用DFS版的SPFA

  • 题目给出的数据不一定是强联通图,即我们可能从图中任意一个点出发都无法到达图上所有点,所以需要另外建一个点,连接这个点到图上所有点的有向边即可。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 7010, M = 20010;
int n, m, tot, head[N], ver[M << 1], nex[M << 1], V[M << 1], P[M << 1], vis[N];
double l = 0, r = 200, mid, dis[N];
inline void add (int x, int y, int v, int p) {
	ver[++ tot] = y;
	V[tot] = v;
	P[tot] = p;
	nex[tot] = head[x];
	head[x] = tot;
}
bool spfa (int x) {
	vis[x] = 1;
	for (int i = head[x]; i; i = nex[i]) {
		int y = ver[i];
		double edge = (double) P[i] * mid - (double) V[i];
		if(dis[y] > dis[x] + edge) {
			if (vis[y]) return false;
			dis[y] = dis[x] + edge;
			if (!spfa(y)) return false;
		}
	}
	vis[x] = 0;
	return true;
}
int main () {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i ++) {
		int x, y, v, p;
		scanf("%d%d%d%d", &x, &y, &v, &p);
		add(x, y, v, p);
	}
	for (int i = 1; i <= n; i ++)
		add(0, i, 0, 0);
	while (l + 0.00001 < r) {
		mid = (l + r) / 2;
		memset(vis, 0, sizeof(vis));
		memset(dis, 0x3f, sizeof(dis));
		vis[0] = 1;
		dis[0] = 0;
		if (spfa(0))
			r = mid;
		else
			l = mid;
	}
	if (!l) printf("-1");
	else printf("%.1lf", l);
	return 0;
}
posted @   duoluoluo  阅读(289)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示