差分约束学习记录
概念
有整数数列 \(\{x_n\}\) 满足形如 \(x_i \le x_j + w\) 的不等式组,称为差分约束系统。我们一般需要输出一组 \(\{x_n\}\) 的可行解,或报告无解。
解法
模板
我们观察不等式,发现与图论中最短路的三角不等式 \(dis_u \le dis_v + w\) 非常像。于是我们考虑图论建模:
将每一个不等式 \(x_i \le x_j + w\) 中,点 \(j\) 向点 \(i\) 连一条权值为 \(w\) 的边,然后任意挑选一个源点(一般为 1),跑最短路。显然,最短路数组 \(dis\) 就是一组可行的解。当且仅当图中有负环时,原不等式组无解。
Q:为什么负环一定无解?
A:负环意味着有如下一组不等式:
且 \(\sum_{j=1}^{k} w_j < 0\)。
将这 \(k\) 个不等式相加,可得到 \(0 \le \sum_{j=1}^{k} w_j\),显然矛盾。
有其他限制(例如值域)或要最大化最小化一些东西的时候,建立超级源点,向其他每个点连权为 \(0\) 的边,跑最短路是最保险的。
Tip:一定要把所有限制加全!
性质
若令源点的权值为 \(0\),那么点 \(i\) 算出来的 \(dis_i\) 就是在此条件下 \(i\) 能取的最大值。证明是显然的,最短路就是满足了一堆限制的上限,故正确。
变形
- 对于 \(x_i \ge x_j + w\) 的形式,只需变形成 \(x_j \le x_i + (-w)\) 的形式,正常建边就行。
- 对于 \(x_i = x_j + w\) 的形式,我们拆成 $x_i \le x_j + w \ \wedge \ x_i \ge x_j + w $。
- 对于 \(w\) 非常小的情形,可以观察性质。如:只有 \(\left | x_i - x_j \right |=1\) 的形式,其等价于 \(-1 \le x_i - x_j \le 1 \wedge x_i \ne x_j\),直接做的话,最短路跑出来恰好是符合的。但是在做之前,我们要判是否是二分图(保证能奇偶染色),不是的话就无解。CF1450E
- 最大化极差,我们可以枚举源点 \(s\) 依次跑最短路,此时用最大的 \(dis_p - dis_s\) 更新极差是不劣的。当然,算出最大的 \(dis_p\),最小的 \(dis_q\),两者相减更新答案也可,但是没必要。CF1450E
- 算 \(x_i\) 最多有多少种不同的取值,就将最优往极差方向靠。具体题目具体分析。luogu P3530
例题
Gym102394A
二分答案 \(t\) 之后就是 naive 题。
具体地,用前缀和 \(\{s_n\}\) 方便地记录限制。
首先,有 \(\forall i \in \{1,2,3,...,n\},0 \le s_i-s_{i-1} \le 1\)。
其次,有 \(s_0=0\)(直接把 \(s_0\) 当源点即可)、\(s_n-s_0=t\)。
另外,对于第一类限制,有 \(s_r - s_{l-1} \ge k\);对于第二类限制,有 \(t - (s_r - s_{l-1}) \ge k\),都是差分约束的形式。
此处二分答案的作用:将第二类限制变得可做。
时间复杂度 \(O(nm \log n)\)。用不着手法,随便写写,CF 上 500ms,QOJ 上 340ms,稳过。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int>
#define all(v) v.begin(), v.end()
using namespace std;
//#define filename "xxx"
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)
#define multi_cases 1
namespace Traveller {
const int N = 3002;
int n, m1, m2;
struct edge {
int u, v, w;
edge() { }
edge(int u, int v, int w) : u(u), v(v), w(w) { }
} e[N << 2];
int tot;
struct constraints {
int l, r, k;
constraints() { }
constraints(int l, int r, int k) : l(l), r(r), k(k) { }
} a[N], b[N];
int dis[N];
int check(int t) {
tot = 0;
for(int i = 1; i <= m1; ++i) {
auto [l, r, k] = a[i];
e[++tot] = edge(r, l-1, -k);
}
for(int i = 1; i <= m2; ++i) {
auto [l, r, k] = b[i];
e[++tot] = edge(l-1, r, t-k);
}
for(int i = 1; i <= n; ++i) e[++tot] = edge(i, i-1, 0), e[++tot] = edge(i-1, i, 1);
e[++tot] = edge(0, n, t), e[++tot] = edge(n, 0, -t);
memset(dis, 0x3f, sizeof(dis));
dis[0] = 0;
for(int i = 1; i <= n+1; ++i) {
int flag = 0;
for(int j = 1; j <= tot; ++j) {
auto [u, v, w] = e[j];
if(dis[u] + w < dis[v]) dis[v] = dis[u] + w, flag = 1;
}
if(flag == 0) break;
if(i == n+1 && flag == 1) return false;
}
return true;
}
void main() {
cin >> n >> m1 >> m2;
for(int i = 1, l, r, k; i <= m1; ++i) {
cin >> l >> r >> k;
a[i] = constraints(l, r, k);
}
for(int i = 1, l, r, k; i <= m2; ++i) {
cin >> l >> r >> k;
b[i] = constraints(l, r, k);
}
int L = 0, R = n;
while(L < R) {
int mid = L + R >> 1;
if(check(mid)) R = mid;
else L = mid+1;
}
cout << L << '\n';
}
}
signed main() {
#ifdef filename
FileOperations();
#endif
signed _ = 1;
#ifdef multi_cases
scanf("%d", &_);
#endif
while(_--) Traveller::main();
return 0;
}

浙公网安备 33010602011771号