负环
什么是负环?
负环的定义是在一个环中,其环上的权值和小于。
怎么判断负环?
根据负环的定义,我们知道在有负环的图中不存在最短路,因为你可以绕着负环一直跑,而你的路径和却会越来越少!
所以如果我们在有负环的图上跑SPFA,会陷入死循环的!
正因为这点,所以可以用SPFA来判断负环。
用表示这个节点入队了几次(也就是这个节点的最短路被更新了几次),若图中有个节点,那么每个节点最多次入队,当入队次数,即时,退出循环,并确定图中有负环。
试题
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;
}
天路:
这题第一眼看,跟负环有什么关系呢?
先别急,根据题意,我们知道题目要求的是某个环使得的值最大,设这个最大值,即答案为。
显然对于图上所有环,,把这个式子变形得到。
因此如果我们把图中每条边的边权设置为,应该得到的是一张没有负环的图。
同时我们知道是满足单调性的,比如样例中的答案,所有大于的答案都是可以使得这张图没有负环的。
但是由于必须要等于某个环的,所以可以二分答案,找到满足条件(不存在负环)且最小的答案。
但是对于这题有几点需要注意:
-
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】