目录
负环
定义
负环指的是在一个带权图中,由若干边组成的环的边权和为负数。这意味着,如果在图中存在这样的环,那么理论上可以沿着这个环无限行走,每走一圈路径的总权重都会减少。
运用情况
- 最短路径问题:在寻找两个顶点之间的最短路径时,如果图中存在负环,那么最短路径问题可能没有解,因为路径的长度可以无限减小。
- 资源分配和经济模型:在某些经济学模型中,负环可能代表某种循环交易机制,其中参与者可以持续获利而无需投入额外资源。
注意事项
- 算法失效:多数最短路径算法(如Dijkstra算法)假设图中没有负权重边,因此它们在处理含有负环的图时可能会产生错误的结果。
- 检测负环:需要使用能够处理负权重边的算法,如Bellman-Ford算法或SPFA算法来检测负环的存在。
- 性能影响:检测负环或在含有负环的图中寻找最短路径通常需要更多的计算资源,时间复杂度可能较高。
解题思路
-
使用Bellman-Ford算法:
- 这个算法可以在O(VE)的时间复杂度内检测出图中是否存在负环。
- 通过V-1次松弛操作,可以得到从源点到所有其他点的最短路径。
- 在第V次松弛操作中,如果还能进一步缩短路径,那么说明图中存在负环。
-
使用SPFA算法(Shortest Path Faster Algorithm)检测负环:
- SPFA是一种基于宽度优先搜索(BFS)的改进算法,可以处理有向图和负权重边。
- 通过记录每个顶点入队的次数或路径上的边数,可以检测到负环。如果某个顶点的入队次数超过顶点总数或路径上的边数超过顶点总数,那么存在负环。
-
Floyd算法虽然可以解决所有对之间的最短路径问题,但在检测负环方面不如Bellman-Ford和SPFA算法直接,因为它主要用于计算最短路径而非检测负环。
在实际应用中,如果怀疑图中可能存在负环,应该首先使用Bellman-Ford或SPFA算法进行检测。如果确认图中存在负环,那么通常意味着问题需要重新定义或需要采取其他措施来避免或处理负环带来的影响。
AcWing 361. 观光奶牛
题目描述
运行代码
#include <cstring>
#include <iostream>
using namespace std;
const int N = 1010, M = 5010;
int n, m;
int wf[N];
int h[N], e[M], ne[M], wt[M], idx;
int q[N], cnt[N];
double dist[N];
bool st[N];
void add(int a, int b, int c) {
e[idx] = b, wt[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
bool check(double x) {
memset(st, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
memset(dist, 0, sizeof dist);
int hh = 0, tt = 0;
for (int i = 1; i <= n; i++) {
q[tt++] = i;
st[i] = true;
}
while (hh != tt) {
int t = q[hh++];
if (hh == N)
hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if (dist[j] < dist[t] + wf[t] - x * wt[i]) {
dist[j] = dist[t] + wf[t] - x * wt[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n)
return true;
if (!st[j]) {
q[tt++] = j;
if (tt == N)
tt = 0;
st[j] = true;
}
}
}
}
return false;
}
int main() {
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> wf[i];
while (m--) {
int a, b, c;
cin >> a >> b >> c;
add(a, b, c);
}
double l = 0, r = 1000;
while (r - l > 1e-4) {
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
printf("%.2lf\n", l);
return 0;
}
代码思路
使用SPFA算法(Shortest Path Faster Algorithm)检测和处理含负权重边的图中是否存在负环,并找到最小流量阈值,使得图中不再存在负环。具体来说:
-
输入读取:读取顶点数
n
和边数m
,以及每个顶点的权重wf[]
。 -
构建图:使用邻接表存储图结构,
add
函数用于添加边。 -
SPFA算法实现:
- 使用
check
函数检测是否存在负环,参数x
是流量阈值。 - 初始化队列
q
和状态数组st
、cnt
、dist
。 - 将所有顶点加入队列,并标记为已访问。
- 进行SPFA算法循环,直到队列为空。
- 队首顶点出队,更新其邻居节点的距离。
- 如果发现更新后的距离更优,更新距离,增加该顶点的
cnt
计数,检查是否形成负环(cnt[j] >= n
)。 - 如果未访问过且距离更新,则加入队列。
- 如果
cnt[j] >= n
则存在负环,返回true
。
- 使用
-
二分查找最小流量阈值:
- 初始化搜索范围
l = 0
,r = 1000
。 - 使用二分查找找到最小的流量阈值
x
,使得图中不再存在负环。
- 初始化搜索范围
改进思路
-
提高精度控制:当前代码使用了固定精度
1e-4
作为二分查找的终止条件,这可能在某些极端情况下不够精确。可以考虑动态调整精度或使用更精确的数值比较策略。 -
优化邻接表存储:当前使用的是链式前向星结构,但在频繁插入边的情况下,可能会有更高的空间开销。可以考虑使用更紧凑的数据结构或优化内存管理。
-
增强代码可读性和维护性:增加注释,使用更明确的变量命名,封装函数逻辑,如将二分查找逻辑封装成独立函数。