适用条件: 边权有正有负有零
判负环: 如果存在负环,那么spfa将一直跑不出结果,因此只需要考虑如果两个点之间有n个点,那么由抽屉原理,必然存在负环.常用的方法为spfa判断负环,该方法在一般的图中,复杂度为O(km),但理论时间复杂度为O(nm)
判正环: 判断正环的思路相反:在i和j之间跑最长路,一旦i和j之间的点的数目大于等于n,认为出现正环
技巧:
1.技巧1:有时候判断负环容易超时,因为一个要让两个点间的点数大于等于n的时候比较费时,所以可以去记录一下当前进入队列的点的总数count,一旦这个总数比较大的时候.比如这个点数count>=2n时,我们认为很大概率存在负环;
2.技巧2:把队列换成栈,一旦存在负环,那么使用栈来处理能够更快得到一个负环
spfa算法明确:
适用条件: 边权全部>=0(或全部<=0)
判负环/正环: tarjan跑scc,然后缩点,判断每个超级点内是否存在大于0(小于0)的边,如果存在说明存在正环(负环)。
适用条件: 边权全部>0(或全部<0)
判断正环/负环: 跑拓扑排序算法,如果最后拓扑序列内数目==n,那么有解,无正环/负环,否则存在正环/负环。
差分约束问题就是求解一组不等式。当题目给定的条件可以转化为不等式组的时候就是求解差分约束。当求最小值,跑最长路;求最大值,跑最短路。同时一旦发现正(负)环那么无解。
差分约束的步骤:
acwing904虫洞
题意: 判断图中是否存在负环
代码:
#include
using namespace std;
int const N = 5e2 + 10, M = 3e6 + 10;
int e[M], ne[M], w[M], idx, h[N], t, n, m, wi, dist[N], cnt[N], st[N];
// 建邻接表
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// spfa求负环(正环)
bool spfa() {
queue<int> q;
memset(dist, 0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
// 思路1:在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。执行到这一步,就等价于把全部点放入队列。这样cnt维护的就是到虚拟源点的距离。
// 思路2:spfa算法一开始加入多少个点到队列都没有关系,因为这些点一开始距离都是无穷,不会引起答案差异。但是由于判断负环,必须把所有点都加入,防止出现孤立连通块的情况。这样cnt维护的就是和某些虚拟点的距离。
for (int i = 1; i <= n; i ++ ) {
st[i] = true;
q.push(i);
}
// dist[0] = 0, st[0] = 1, q.push(0); 如果希望能够正确求出dis数组,那么还需要加上这个代码
while (q.size()) {
int t = q.front(); // 取队首
q.pop(); // 出队首
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[t] + w[i]) {
// 这里是判断负环,如果是判正环:1.初始化写成memset(dis, 0xc0, sizeof dis), 2.更新条件写成dist[j] < dist[t] + w[i]
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1; // 更新边数
if (cnt[j] >= n) return true; // 如果j点到源点的边数大于等于n
if (!st[j]) {
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main() {
cin >> t;
while (t--) {
cin >> n >> m >> wi;
idx = 0;
memset(h, -1, sizeof h);
for (int i = 1, a, b, c; i <= m; ++i) {
scanf("%d %d %d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
for (int i = 1, a, b, c; i <= wi; ++i) {
scanf("%d %d %d", &a, &b, &c);
add(a, b, -c);
}
if (spfa()) printf("YES\n");
else printf("NO\n");
}
return 0;
}
acwing361观光奶牛
题意: 给定一张L个点、P条边的有向图,每个点都有一个权值f[i],每条边都有一个权值t[i]。求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。输出这个最大值。 点数N~1e3, 边数M~5e3
题解: 本题是最大比率环+01分数规划,方法为二分 + 构图跑负环。即枚举二分枚举答案,然后根据这个mid来重新构图:把每个点和这个点对应的一条出边对应起来作为环上的一条边,具体操作就是当对t点的所有出边进行更新的时候,原来的边权w[i],变为mid*w[i] - f[t]。这样把点权放到每个出边的边权上。然后跑spfa判断是否存在负环即可
代码:
#include
using namespace std;
int const N = 1e3 + 10, M = 5e5 + 10;
double const eps = 1e-8;
int e[M], ne[M], w[M], idx, h[N], n, m, cnt[N], st[N], f[N];
double dist[N];
// 建邻接表
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
// spfa求负环(正环)
bool spfa(double mid) {
queue<int> q;
memset(dist, 0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
memset(st, 0, sizeof st);
for (int i = 1; i <= n; i ++ ) {
st[i] = true;
q.push(i);
}
// dist[0] = 0, st[0] = 1, q.push(0); 如果希望能够正确求出dis
while (q.size()) {
int t = q.front(); // 取队首
q.pop(); // 出队首
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[t] + w[i] * mid - f[t]) {
// 这里是判断负环,如果是判正环:1.初始化写成memset(dis, 0xc0, sizeof dis), 2.更新条件写成dist[j] < dist[t] + w[i]
dist[j] = dist[t] + w[i] * mid - f[t]; // 边权发生改变
cnt[j] = cnt[t] + 1; // 更新边数
if (cnt[j] >= n) return true; // 如果j点到源点的边数大于等于n
if (!st[j]) {
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main() {
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; ++i) scanf("%d", &f[i]);
for (int i = 1, a, b, c; i <= m; ++i) {
scanf("%d %d