【知识点复习】负环与差分约束

前言

写博客写麻了。网课令人身心俱疲

参考链接算法学习笔记(11): 差分约束

负环

负环:这还用解释?

最短路里面有提到过,这里就不赘述了(主要是啊呸摆)。

差分约束系统

定义:一种特殊的 \(N\) 元一次不等式组,包含 \(N\) 个变量,\(M\) 个约束条件, 每个约束条件都是有两个变量作差构成的, 形如 \(X_i - X_j <= C_k\)

对于差分约束系统而言,其中的每个约束条件都可以看做一条由点 \(j\) 连向点 \(i\) 的长为 \(C_k\)有向边

注意到如果 { \(a_1, a_2, …, a_n\) } 为一组解,

那么对于任意常数 \(t\),{ \(a_1 + t, a_2 + t, …, a_n + t\) } 也是一组解。

所以,不妨先求一组负数解,然后再增加一个 \(0\) 号节点, 令 \(X_0 = 0\)

这样一来,就多增加了 \(N\) 条从节点 \(0\) 连向节点 \(1 ~ n\) 的有向边。

\(dis[0] = 0\),套进求单源最短路,\(X_i = dis[i]\) 即为一组解。

另外,如果图中存在负环,说明该差分约束系统无解。

应用

观光奶牛

题目传送门

给定一张 L 个点、P 条边的有向图,每个点都有一个权值 f[i],每条边都有一个权值 t[i]。

求图中的一个环,使“环上各点的权值之和”除以“环上各边的权值之和”最大。

输出这个最大值。

注意:数据保证至少存在一个环。

同样是二分答案。

因为题目保证至少存在一个环,所以不用考虑成环的问题。

因此“环上各点的权值之和 除以 环上各边的权值之和” 可以暂时理解成 “单个点的边权 除以 点权”, 下文记作 \(mid\)

检验时,通过二分的 \(mid\) 求出理想边权(非实际),然后建立差分约束系统,通过记录每个点的点权改变次数判断是否存在负环 ,如果存在负环,说明差分约束无解,答案应该缩小;否则答案有增大的可能。

点击查看代码
#include<cstdio>
#include<algorithm>
using namespace std;
const double eps = 1e-4;
const int N = 1005, M = 5005;
int n, m, q[N * M], a[N], cnt[N];
int tot, head[N], nex[M], to[M], w[M];
bool vis[N];
double dis[N];
void add(int x, int y, int z) {
	to[++tot] = y, nex[tot] = head[x], head[x] = tot, w[tot] = z;
}

bool check(double mid) {
	int now, ver, st = 0, ed = -1;
	double tmp;
	for(int i = 1; i <= n; i ++) vis[i] = 1, dis[i] = cnt[i] = 0, q[++ed] = i;
	while(st <= ed) {
		now = q[st ++], vis[now] = 0;
		for(int i = head[now]; i; i = nex[i]) {
			ver = to[i], tmp = w[i] * mid - a[now];
			if(dis[ver] > dis[now] + tmp) {
				dis[ver] = dis[now] + tmp;
				cnt[ver] = cnt[now] + 1;
				if(cnt[ver] >= n) return 1;
				if(!vis[ver]) q[++ed] = ver; 
			}
		}
	}
	return 0;
}


int main() {
	scanf("%d %d", &n, &m);
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
	int u, v, z;
	while(m --) {
		scanf("%d %d %d", &u, &v, &z);
		add(u, v, z);
	}
	double l = 0, r = 1000, mid;
	while(r - l > eps) {
		mid = (l + r) / 2;
		if(check(mid)) l = mid;
		else r = mid;
	}
	printf("%.2lf", r);
	return 0;
}

区间

题目传送门

给定 n 个区间 [ai,bi] 和 n 个整数 ci 。

你需要构造一个整数集合 Z,使得 ∀i∈[1,n],Z 中满足 ai≤x≤bi 的整数 x 不少于 ci 个。

求这样的整数集合 Z 最少包含多少个数。

就是个板题,看题可以找的非常明显的条件 \(s[b_i] - s[a_i - 1] >= c_i\),直接连边就好。

但是题目还有一些需要注意的其他约束条件,才能保证求出的解有意义:

  • \(s[i] - s[i - 1] >= 0\) —— \(0 ~ i\) 之间选出的数肯定大于 \(0 ~ i-1\)

  • \(s[i - 1] - s[i] >= -1\) —— 每个数只能选一次;

  • \(s[i] - s[0] >= 0\) —— 显然。

这样建立的差分约束系统才算完整。

记图中的最大标号为 \(mx\)

跑一遍 \(Spfa\), 最后 \(dis[mx]\) 即为所求答案。

点击查看代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N = 5e4 + 5;

int n, tot, mx, head[N], nex[N * 5], to[N *5], w[N *5];

void add(int x, int y, int z) {
	to[++tot] = y, nex[tot] = head[x], w[tot] = z, head[x] = tot;
}

int dis[N];
queue<int> q;
bool vis[N];
void Spfa(int s) {
	memset(dis, -0x3f, sizeof(dis));
	dis[s] = 0, vis[s] = 1, q.push(s);
	int now, ver;
	while(!q.empty()) {
		now = q.front(), q.pop(), vis[now] = 0;
		for(int i = head[now]; ~i; i = nex[i]) {
			ver = to[i];
			if(dis[ver] < dis[now] + w[i]) {
				dis[ver] = dis[now] + w[i];
				if(!vis[ver]) {
					vis[ver] = 1;
					q.push(ver);
				}
			}
		}
	}
}

int main() {
	scanf("%d", &n);
	int a, b, c;
	memset(head, -1, sizeof(head));
	for(int i = 1; i <= n; i ++) {
		scanf("%d %d %d", &a, &b, &c), b ++;
		mx = max(max(a, b), mx);
		add(0, b, 0), add(0, a, 0), add(a, b, c);
	}
	for(int i = 1; i <= mx; i ++) add(i - 1, i, 0), add(i, i - 1, -1);
	Spfa(0);
	printf("%d", dis[mx]);
	return 0;
}
posted @ 2022-07-12 10:52  Spring-Araki  阅读(57)  评论(0编辑  收藏  举报