一.差分约束系统
如果一个系统有 n n n 个变量 x 1 , x 2 , ⋅ ⋅ ⋅ , x n x_1,x_2,···,x_n x1,x2,⋅⋅⋅,xn 和 m m m 个约束条件(也是不等式)和** m m m 个常量 w 1 , w 2 , ⋅ ⋅ ⋅ , w m w_1,w_2,···,w_m w1,w2,⋅⋅⋅,wm。每一个不等式形如以下格式 x i − x j ≤ w k x_i - x_j \le w_k xi−xj≤wk( 1 ≤ i , j ≤ n 1 \le i,j \le n 1≤i,j≤n, 1 ≤ k ≤ m 1 \le k \le m 1≤k≤m)。则称之为差分约束系统。**
这个名字的由来是这样的:“差分”代表的是每一个约束条件里面都是做差的形式,“约束”代表的是每一个差都有一个最大值上界作为约束,“系统”字面意思。
有如下几个问题:
1.有没有解
2.求一组解
3.一组变量( a − b a - b a−b)的极值
4.在额外条件下,变量和最值
…
容易发现,差分约束系统的解要么就无解,要么就有无数组解。
且这东西很容易识别,如果有一堆不等式每一个都形如 x i − x j ≤ w k x_i - x_j \le w_k xi−xj≤wk 这个格式,那么这个解决方法十有八九就是差分约束了。
这个东西的问题很清晰,接下来我们来看如何解决提出的这几个问题。
二.解的判断与求解
差分约束**做了一个模型转化,将一个不等式组的问题转化为了一个图论问题。**是不是很神奇?
在大学的计算机课程中,有一个东西叫做问题规约,即将一个问题的模型奇妙地转化为了另一个问题的模型。现在我告诉你,你只需要会求解图论中的最短路,你就可以做这个问题了。
我们将这个格式做一个变换:既然 x i − x j ≤ w k x_i - x_j \le w_k xi−xj≤wk,则可以转换为 x i ≤ x j + w k x_i \le x_j + w_k xi≤xj+wk。
如果这时把 x x x 看成就是最短路中的 d i s dis dis 数组,则 d i s i ≤ d i s j + w k dis_i \le dis_j + w_k disi≤disj+wk。
咦,这不就很像最短路中的松弛操作吗?
if (dis[v] > dis[u] + w)
dis[v] = dis[u] + w;
上面的两行代码相当于 dis[v] = min(dis[v], dis[u] + w)
这条语句。
于是我们得知,当进行一次松弛操作的时候,div[v]
一定
≤
\le
≤ dis[u] + w
,即
x
i
x_i
xi 一定满足
≤
x
j
+
w
k
\le x_j + w_k
≤xj+wk,恰好满足不等式!
如果将 w k w_k wk 看成 j → i j \to i j→i 的一条边,则恰好就可以完美吻合!
所以我们尝试将每一个不等式组 x i − x j ≤ w k x_i - x_j \le w_k xi−xj≤wk 变成 j → i j \to i j→i 的一条有向边,**边权为 w k w_k wk。**也就是将每一个约束条件都抽象成了一条边。
这时我们设任意的一个点为 S S S,也就是起点,假设 x S = 0 x_S = 0 xS=0。因为如果有解,就会有无数的解,就一定有 x S = 0 x_S = 0 xS=0 为基础的这一组解。
则如果把该松弛的都松弛完了,也就是跑完了全图的从 S S S 开始的单源最短路,则如果 ∀ 1 ≤ i ≤ n , x i = d i s i \forall 1 \le i \le n,x_i = dis_i ∀1≤i≤n,xi=disi,对于任何的不等式都一定满足。
注意,这时候的图包含负权边(谁说 w k w_k wk 不能是负数了?),不能使用 dijkstra,需要使用 spfa 算法。
那么如何判断有没有解呢?
这个问题很简单,在上文的字里行间中就存在答案,这个字眼就是“跑完了单源最短路”。如果有某一个点的最短路不存在,则就是无解的。
什么?最短路不存在?你可以想到,不存在最短路,也就是无解,当且仅当图包含负环。
恰好 spfa 又可以判负环,于是在 spfa 求解最短路的过程中顺便判一下负环即可。
举一个例子:
这样的图中显然存在负环,不妨将其重新抽象成差分约束系统:
$$
\begin{matrix}
x_1 - x_3 \le -2 \
x_3 - x_2 \le -2 \
x_2 - x_1 \le 3 \
\end{matrix}
$$
不妨将第一个式子和第二个式子联立起来,得到: x 1 − x 2 ≤ − 4 x_1 - x_2 \le -4 x1−x2≤−4,两边同时取相反数得到: x 2 − x 1 ≥ 4 x_2 - x_1 \ge 4 x2−x1≥4,但是这样又和第三个式子矛盾了,所以这个差分约束系统无解。
这样我们就同时解决了第一个和第二个问题。
三.特殊处理
看上去我们解决了第二个问题,但实际上第二个问题还没有结束呢!
2 + 2^{+} 2+:如果没有唯一的起点也就是 S S S,也就是建出的图压根就不弱连通,怎么办呢?
这时候,我们可以弄一个超级源点,向所有点连一条边权为 0 0 0 的边。
但这个时候就有人会问了:那么所有点的最短路值不就全是 0 0 0 了吗?
这时候我们分情况。
如果所有的约束条件的 w w w 都是非负数
这时候所有点的最短路的值的确有可能会全部都是 0 0 0。
但是这个时候任意取两个数 x i − x j x_i - x_j xi−xj 都是 0 0 0,而 0 0 0 显然 ≤ \le ≤ 全体非负数,一定成立。
如果有 w w w 是负数
这个时候最短路的值也有可能被不是 0 0 0。
因为有一个很巧合的点: 0 0 0 显然 ≥ \ge ≥ 全体负数,所以也一定成立。
四.模板
P5960 【模板】差分约束
#include <bits/stdc++.h>
using namespace std;
int n, m;
const int N = 5010;
vector<pair<int, int> > v[N];
int dis[N], cnt[N];
bool vis[N];
bool spfa(int s) {//spfa算法判断负环
queue<int> q;
for (int i = 1; i <= n; i++)
dis[i] = 1e17;
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (auto [to, w] : v[u])
if (dis[to] > dis[u] + w) {
dis[to] = dis[u] + w;
cnt[to] = cnt[u] + 1;
if (cnt[to] >= n)
return 0;//找到负环
if (!vis[u])
q.push(to), vis[to] = 1;
}
}
return 1;
}
int main() {
cin >> n >> m;//这里设n+1号点为超级源点
n++;
for (int i = 1; i < n; i++)
v[n].push_back({i, 0});//连边
for (int i = 1; i <= m; i++) {
int x, y, w;
cin >> x >> y >> w;
v[y].push_back({x, w});//建图
}
if (!spfa(n))
cout << "NO\n";//发现负环就无解了
else {
for (int i = 1; i < n; i++)//输出每一个点的最短路作为一组解
cout << dis[i] << " ";
cout << endl;
}
return 0;
}
P1993 小 K 的农场
题面:有一堆式子和 n n n 的变量,这些式子形如:
第一种格式, x a − x b ≥ c x_a - x_b \ge c xa−xb≥c
第二种格式, x a − x b ≤ c x_a - x_b \le c xa−xb≤c
第三种格式, x a = x b x_a = x_b xa=xb
这下虽然不是普通的差分约束系统,但我们还是可以将其进行一些变换:
x a − x b ≥ c x_a - x_b \ge c xa−xb≥c 可以变成 x b − x a ≤ − c x_b - x_a \le -c xb−xa≤−c,符合基本格式。
x a − x b ≤ c x_a - x_b \le c xa−xb≤c 本来就可以使用差分约束求解。
x a = x b x_a = x_b xa=xb 可以变成 x a − x b ≤ 0 , x b − x a ≤ 0 x_a - x_b \le 0,x_b - x_a \le 0 xa−xb≤0,xb−xa≤0,也符合基本格式。
这样就可以变成正宗的差分约束系统了。
int main() {
cin >> n >> m;
n++;
for (int i = 1; i < n; i++)
v[n].push_back({i, 0});
for (int i = 1; i <= m; i++) {
int op;
cin >> op;
if (op == 1) {
int x, y, w;
cin >> x >> y >> w;
v[x].push_back({y, -w});
} else if (op == 2) {
int x, y, w;
cin >> x >> y >> w;
v[y].push_back({x, w});
} else {
int x, y;
cin >> x >> y;
v[x].push_back({y, 0});
v[y].push_back({x, 0});//建图
}
}
if (!spfa(n))
cout << "No\n";
else
cout << "Yes\n";
return 0;
}
四.第三个问题
3.求 a − b a-b a−b 的最大或最小值。
最大值:以 b b b 为起点跑最短路, d i s a dis_a disa 就是答案。
最小值:以 b b b 为起点跑最长路, d i s a dis_a disa 就是答案。
五.第四个问题
4.额外条件下,变量和最大最小值
额外条件是因为差分约束可能有无穷组解,所以必须做出额外约束。
最大值:超级源点跑最短路,把所有的 d i s i dis_i disi 加在一起就是答案。