差分约束
差分约束系统用于求解 \(n\) 个未知数 \(x_1 \sim x_n\),\(m\) 个形如 \(x_u - x_v \le w\) 也就是 \(x_u \le x_v + w\) 的不等式的解。
两种建图方式:
- \(v\) 向 \(u\) 连边权为 \(w\) 的有向边,建立虚点 \(0\) 向每个点连边权为 \(0\) 的有向边,从点 \(0\) 开始跑最短路,得到的是每个 \(x_u \le 0\) 且 \(x_u\) 的最大值,如果图中有负环则无解。
- \(u\) 向 \(v\) 连边权为 \(-w\) 的有向边,建立虚点 \(0\) 向每个点连边权为 \(0\) 的有向边,从点 \(0\) 开始跑最长路,得到的是每个 \(x_u \ge 0\) 且 \(x_u\) 的最大值,如果图中有负环则无解。
例题:P5960 【模板】差分约束
参考代码
#include <cstdio>
#include <vector>
#include <queue>
#include <utility>
const int N = 5005;
const int INF = 1e9;
std::vector<std::pair<int, int>> g[N];
int cnt[N], dis[N];
bool inq[N];
int main()
{
int n, m; scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int c1, c2, y;
scanf("%d%d%d", &c1, &c2, &y);
g[c2].push_back({c1, y});
}
for (int i = 1; i <= n; i++) {
dis[i] = INF;
g[0].push_back({i, 0});
}
std::queue<int> q;
q.push(0); dis[0] = 0; inq[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop(); inq[u] = false;
for (auto e : g[u]) {
int v = e.first, w = e.second;
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n + 1) { // 注意多了一个虚点0
printf("NO\n"); return 0;
}
if (!inq[v]) {
q.push(v); inq[v] = true;
}
}
}
}
for (int i = 1; i <= n; i++) printf("%d ", dis[i]);
return 0;
}
例题:P1993 小 K 的农场
- \(x_a - x_b \ge c\) 实际上就是 \(x_b - x_a \le -c\),可以让 \(a\) 向 \(b\) 连一条边权为 \(-c\) 的边
- \(x_a - x_b \le c\) 是差分约束系统的标准形式,让 \(b\) 向 \(a\) 连一条边权为 \(c\) 的边
- \(x_a = x_b\) 也可以转化成不等式关系,即 \(x_b - x_a \le 0\) 且 \(x_a - x_b \le 0\),让 \(a\) 和 \(b\) 之间互相连边权为 \(0\) 的边
参考代码
#include <cstdio>
#include <vector>
#include <utility>
#include <queue>
const int N = 5005;
const int INF = 1e9;
std::vector<std::pair<int, int>> g[N];
int cnt[N], dis[N];
bool inq[N];
int main()
{
int n, m; scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int t, a, b, c; scanf("%d%d%d", &t, &a, &b);
if (t != 3) scanf("%d", &c);
if (t == 1) g[a].push_back({b, -c});
else if (t == 2) g[b].push_back({a, c});
else {
g[a].push_back({b, 0}); g[b].push_back({a, 0});
}
}
for (int i = 1; i <= n; i++) {
g[0].push_back({i, 0}); dis[i] = INF;
}
std::queue<int> q;
q.push(0); inq[0] = true; dis[0] = 0;
while (!q.empty()) {
int u = q.front(); q.pop(); inq[u] = false;
for (auto e : g[u]) {
int v = e.first, w = e.second;
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n + 1) {
printf("No\n"); return 0;
}
if (!inq[v]) {
q.push(v); inq[v] = true;
}
}
}
}
printf("Yes\n");
return 0;
}
习题:P1260 工程规划
给定包含 \(n \ (5 \le n \le 1000)\) 个子任务的工程,每个任务的起始时间为 \(T_1, T_2, \dots, T_n\)(均为非负整数,代表整个工程开始后的启动时间)。另外给定 \(m \ (5 \le m \le 5000)\) 个限制条件,每个限制条件形如不等式 \(T_i - T_j \le b\)。
任务是:
- 找出一种可能的起始时间序列 \(T_1, T_2, \dots, T_n\)。
- 要求最早进行的任务起始时间必须与整个工程的起始时间相同,即 \(T_1, T_2, \dots, T_n\) 中至少有一个为 \(0\)。
- 若无可行方案,输出
NO SOLUTION。
解题思路
\(T_i - T_j \le b\) 可以变形为 \(T_i \le T_j + b\),这与图论中单源最短路的三角不等式 \(d(v) \le d(u) + w(u,v)\) 形式完全一致。因此,可以将每个任务看作图中的一个顶点:对于每个不等式 \(T_i - T_j \le b\),从顶点 \(j\) 向顶点 \(i\) 连一条有向边,边的权值为 \(b\);如果图中存在 负权环,则说明不等式组无解,输出 NO SOLUTION。
由于图可能不是强连通的,引入一个 超级源点 \(0\)。从超级源点 \(0\) 向每个任务节点 \(1 \le i \le n\) 连一条边权为 \(0\) 的有向边,代表不等式 \(T_i - T_0 \le 0\)。设定 \(T_0 = 0\),这样在求最短路时,所有的最短路初始上限都会被约束在 \(0\) 以下,即 \(T_i \le 0\)。同时,从源点 \(0\) 出发可以遍历到图中的所有节点,从而能够检测出图中的任意负权环。
如果未检测到负环,会求得一组解 \(d_1, d_2, \dots, d_n\)。以超级源点为基准算出的最短路值 \(d_i \le 0\),而答案需要满足所有的起始时间均为非负整数(\(T_i \ge 0\))和至少有一个任务的起始时间为 \(0\)。因为差分约束系统的解具有 平移不变性(即若 \(T\) 是一组解,则对任意常数 \(C\),\(T+C\) 也是一组解),可以找出这组解中的最小值 \(\min \{ d \}\),然后将每个节点的起始时间修正为 \(T_i = d_i - \min \{ d \}\)。这样修正后的解不仅满足所有差分约束条件,而且保证了所有起始时间均非负,且至少有一个(即取得最小值的节点)起始时间为 \(0\)。
参考代码
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 1005;
const int INF = 1e9;
struct Edge {
int to, w;
};
vector<Edge> a[N];
int d[N], cnt[N];
bool inq[N];
int main()
{
int n, m; scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
// T_u - T_v <= w => T_u <= T_v + w
// 建立从 v 到 u,边权为 w 的有向边
a[v].push_back({u, w});
}
for (int i = 1; i <= n; i++) {
// 增加超级源点 0,向每个顶点 i 连一条权值为 0 的边
a[0].push_back({i, 0});
d[i] = INF;
}
queue<int> q;
q.push(0); inq[0] = true;
while (!q.empty()) {
int u = q.front(); q.pop(); inq[u] = false;
for (Edge e : a[u]) {
int v = e.to, w = e.w;
if (d[u] + w < d[v]) {
d[v] = d[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] > n) {
printf("NO SOLUTION\n");
return 0;
}
if (!inq[v]) {
q.push(v); inq[v] = true;
}
}
}
}
int mind = INF;
for (int i = 1; i <= n; i++) mind = min(mind, d[i]);
for (int i = 1; i <= n; i++) printf("%d\n", d[i] - mind);
return 0;
}

浙公网安备 33010602011771号