Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
title: 差分约束 categories:
- 常用算法
- 图论 tags:
- 差分约束
对于一系列形如的不等式组,其中走到都是自变量,是一个常数。
考虑我们的最短路算法,假设从走到存在一条长度为的边,那么跑完最短路算法后,就必然有以下三角不等式成立:
原因很简单,因为当时,会被更新,因此对于每一个不等式,我们可以连一条从到权值为的的边,然后跑一遍最短路算法,获得一个可行解。
跑最短路算法就需要考虑图中存在负环的情况,因为负环图中不存在最短路,考虑图中边和不等式的对应关系如果有负环,就意味着有以下不等式成立:
假设三个点构成负环:
则有:
因为是负环,因此三条边之和小于零,推出矛盾:
由此可见,图中存在负环,等价于原不等式组无解,类似的思考流程可以推广到任意个点的负环。
另外我们也可以从最长路角度考虑,类似的,假设从走到存在一条长度为的边,那么跑完最长路算法后,就必然有以下三角不等式成立:
因此我们需要对原不等式进行些许变化:
我们可以连一条从到权值为的的边,然后跑一遍最短路算法,获得一个可行解。
以最短路方法为介绍一下具体步骤:
-
差分约束求不等式组的可行解
源点需要满足的条件:从源点出发可以定可以走到所有的边,这是为了保证每一个不等式条件都能够被考虑到。一般来说,只要从源点出发能到所有点,则可以推出源点能到所有边,反之不一定成立,因为可能存在孤立点没有任何边与它相连。
步骤:- 先将每个不等式,转化成一条从走到,长度为的一条边;
- 找一个超级源点,使得该源点一定可以遍历到所有边;
- 从源点求一遍单源最短路:
结果1:如果存在负环,则原不等式组一定无解;
结果2:如果没有负环,则就是原不等式组的一个可行解;
-
如何求最大值或最小值(指每个变量的最值)
结论:如果求的是最小值,则应该求最长路,如果求的是最大值,则应该求最短路。
对于求最值的问题一般来说,所有的点都有一个绝对值的限制,比如是所有点满足 或者 ,否则只有相对关系的话,是无法求出最值的。
那么引入问题1:如何转化 或者 ,其中是一个常数(这类不等式只有一个变量)
方法:建立一个超级源点,点值为0,然后建立从0到,长度为的边即可以求最大值为例,对于一个来说,一定存在一系列的链式关系,通过不断放缩得到一个最终和超级源点相关的不等式,如下:
为了求到的上界,我们需要找到所有从出发构成的的不等式链所计算出的的上界,然后在这些上界中取一个最小值,这样子就是我们的所能够取到的上界,即最大值。
根据前文分析,每一个不等式链都可以对应到从0号点出发走到i号点的一个路径,而求上界的最小值,即等价于求图中的最短路。
求最小值的思想也很对称,不再展开。
总结:
求不等式组的可行解,要找到一个超级源点,使得该源点一定可以遍历到所有边,然后从源点求单源最短路,如果存在负环,则原不等式组一定无解,如果没有负环,从源点到每个点的最短距离即为可行解;
如果是求最值问题,如果求最小值,对应图中求最长路,如果求最大值问题,对应在图中求最短路。
结合一个例题考虑:
Acwing 1169.糖果
幼儿园里有个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的个要求。
幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式 输入的第一行是两个整数, 。
接下来 行,表示分配糖果时需要满足的关系,每行3个数字 ,,。
- 如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
- 如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
- 如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
- 如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
- 如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。 小朋友编号从 1 到 N。
输出格式 输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。
数据范围 1≤N<105, 1≤K≤105, 1≤X≤5, 1≤A,B≤N
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 3 * N;
int h[N], e[M], w[M], ne[M], idx;
//链式前向星存图
void add(int a, int b, int c) {
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dist[N], q[N], cnt[N], tt;
bool st[N];
int n, m;
//栈优化spfa求正环,同时求最长路
bool spfa() {
q[++ tt] = 0;
while(tt) {
int u = q[tt --];
st[u] = false;
for(int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if(dist[j] < dist[u] + w[i]) {
dist[j] = dist[u] + w[i];
cnt[j] = cnt[u] + 1;
if(cnt[j] > n) return true;
if(st[j]) continue;
q[++ tt] = j;
st[j] = true;
}
}
}
return false;
}
int main () {
//加速输入
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
memset(h, -1, sizeof h);
//将不等式关系转化为边
while(m --) {
int x, a, b;
cin >> x >> a >> b;
if(x == 1) add(a, b, 0), add(b, a, 0);
else if(x == 2) add(a, b, 1);
else if(x == 3) add(b, a, 0);
else if(x == 4) add(b, a, 1);
else add(a, b, 0);
}
//从超级源点向所有点建边
for(int i = 1; i <= n; i ++) add(0, i, 1);
if(spfa()) puts("-1");
//会爆int因此用ll
else cout << reduce(dist + 1, dist + n + 1, 0ll) << endl;
return 0;
}
这个题也可以用算法求强连通分量,然后缩点,再求最长路搞定,强连通分量中存在边权为1的边,则意味着无解,此处不展开。