差分约束系统
update in : 2022.4.16
Part 1 定义
差分约束系统是一种特殊的 \(N\) 元一次不等式组,它包含 \(N\) 个变量,\(X1~Xn\) 以及 \(m\) 个约束条件,每一个约束条件都是由两个变量作差构成的,形如 \(X_i-X_j≤C_k\) ,其中 \(C_k\) 是常数,\(1≤i,j≤N,1≤k≤M\),解决的问题是求一组解,\(X_1=a_1\),\(X_2=a_2···X_n=a_n\) ,使得所有的约束条件都得到满足。
差分约束系统的每一个约束条件 \(X_i-X_j≤C_k\) 可以变形为最短路中的三角不等式,\(X_i≤X_j+C_k\) ,因此,可以把每一个变量 \(X_i\) 看作有向图中的一个结点 \(i\) ,对于每一个约束条件 \(X_i-X_j≤C_k\) ,为从结点 \(j\) 想结点 \(i\) 连一条长度为 \(C_k\) 的有向边
Part 2 基本方法
一般来说,需要你求的解可能是最小解或是最大解,这时候就需要我们转化一下了ヽ(ー_ー)ノ。
例如上边的式子,可转化为:\(x_i >= k + x_j\)的形式,可以发现,这种形式很像最短路中的更新方法:
if(dis[y] > dis[x] + w[x][y]) dis[y] = dis[x] + w[x][y];
所以这种约束问题我们就可以用最短路的思想来解决ヾ(๑╹◡╹)ノ"
Part 3 例题
通过几个例题来理解一下最短路求解不等式组的实现方式。
大体题意:给定 \(n\) 个区间,每个区间内至少选 \(c_i\) 个数。
设 \(d[i]\) 表示从 \(0 ~ i\) 中选了几个数
那么这个题就转化成了解 \(n\) 个不等式。
看样例:
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
把它转化成方程组
d[7] - d[2] >= 3
d[10] - d[7] >= 3
d[8] - d[5] >= 1
d[3] - d[0] >= 1
d[11] - d[9] >= 1
通过消元可以得到 \(d[11] - d[0]\) 的最小值为 \(6\)。从 0 到 3 跑一边 SPFA 求最长路,发现答案也是 &6&。
这不是个巧合,因为求最长路时,SPFA的松弛操作后,\(dis[v]>=dis[u]+line[i].w\),与不等式\(X_i>=X_j+C_k\) 性质一样,所以 \(SPFA\) 可以求出最小值,又因为不等式同大取大,所以要求出最长路。
本题就是从 \(0-50000\) 选出最少的数,使每个区间至少有 \(c\) 个数被选,这是求最小值,所以我们待会要将所有的不等式转换成">="的形式 我们用数组\(d[i]\) 表示 \(0-i\) 之间至少要选多少个数,所以对于每一个约束条件,可以看成 \(d[b_i]-d[a_i-1]>=c_id[bi]−d[ai−1]>=ci\)
\(d[b_i]>=c_i+d[a_i−1]\)
因此我们在 \(a[i-1]\) 和 \(b[i]\) 之间建一条长度为 \(c[i]\) 的边
code:
/**
* author: zcxxxxx
* creater: 2022.3.30
**/
#include <bits/stdc++.h>
using namespace std;
const int A = 1e2 + 7;
const int B = 1e3 + 7;
const int C = 1e4 + 7;
const int D = 5e5 + 7;
const int E = 1e6 + 7;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int n, cnt, mn = INF, mx = 0;
int dis[D], head[D];
bool vis[D];
struct edge {
int nxt, to, dis;
}line[E << 1];
void add(int u, int v, int w) {
line[++cnt].nxt = head[u];
line[cnt].to = v;
line[cnt].dis = w;
head[u] = cnt;
}
void spfa() {
queue<int> q;
q.push(0);
dis[0] = 0;
vis[0] = 1;
while(q.size()) {
int x = q.front(); q.pop();
vis[x] = 0;
for(int i = head[x]; i; i = line[i].nxt) {
int y = line[i].to, z = line[i].dis;
if(dis[y] < dis[x] + z) {
dis[y] = dis[x] + z;
if(!vis[y]) {
vis[y] = 1;
q.push(y);
}
}
}
}
}
int main() {
int t = read();
while(t --) {
cnt = 0;
memset(head, 0, sizeof head);
memset(dis, -INF, sizeof dis);
n = read();
for(int u, v, w, i = 1; i <= n; ++ i) {
u = read(), v = read(), w = read();
add(u, v + 1, w);
mx = max(mx, v + 1);
}
for(int i = 1; i <= mx; ++ i) {
add(i - 1, i, 0);
add(i, i - 1, -1);
}
spfa();
cout << dis[mx] << "\n";
if(t) puts("");
}
return 0;
}