如果一個系統由 n 個變數和 m 個約束條件組成,每個約束條件形如 \(x_j-x_i<=b_k\),其中 \(i,j\in[1,n],k\in[1,m]\),則稱其為差分約束系統(System of Difference Constraints)。亦即,差分約束系統是求解關於特殊的 \(N\) 元一次不等式組的方法。
我們先來看一個簡單的數學問題,如下給定 4 個變數和 5 個不等式約束條件,求 \(x_3-x_0\) 的最大值。
我們可以通過不等式的兩兩加得到三個結果,
由以上結果很容易得知,\(x_3-x_0\) 的最大值是 7,也就是上面三式裡的最小值。
這個例子很簡單,只有 4 個變數和 5 個不等式約束條件,那如果有上百變數上千約束條件呢?僅憑肉眼手工計算效率太差,因此我們需要一個較為系統的解決辦法。
我們先來看一幅圖,如下,給定四個小島以及小島之間的有向距離,問從 0 號島到 3 號島的最短距離。箭頭指向的線代表兩個小島之間的有向邊,藍色數字代表距離權值。
這個問題就是經典的最短路問題。由於這個圖比較簡單,我們可以列舉所有的路線,發現總共三條路線,如下:
-
0 -> 3,長度為 8
-
0 -> 2 -> 3,長度為 7 + 2 = 9
-
0 -> 1 -> 2 -> 3,長度為 2 + 3 + 2 = 7
最短路為三條線路中的長度的最小值,即 7,所以最短路的長度就是 7。細心的讀者會發現,這幅圖和最上方的五個不等式約束條件是有所關聯的,但這個關聯並不是巧合,而正是我們接下來要講的那個 "系統的解決辦法"。
差分約束與最短路
差分約束系統中的每個約束條件 \(x_i-x_j<=c_k\) 都可以變形成 \(x_i<=x_j+c_k\),這與單源最短路中的三角形不等式 \(dist[y]<=dist[x]+z\) 非常相似。
因此,我們可以把每個變數 \(x_i\) 看做圖中的一個結點,對於每個約束條件 \(x_i-x_j<=c_k\),看成是從結點 \(j\) 向結點 \(i\) 的一條權值為 \(c_k\) 的有向邊,於是我們就可以把一個差分約束系統轉化成圖的最短路問題。
然而在實際問題中情況往往會複雜得多,例如,把條件約束裡的所有等號去掉,
這個時候我們就需要將上面的小於號轉換成小於等於號。
當 \(x_i\) 被限定只能是整數時,這個轉換就會非常簡單,
總結
差分約束問題下,
- 如果要求最大值,則想辦法把每個不等式變為標準 \(x_i-x_j<=c_k\) 的形式,然後建立一條從 \(j\) 到 \(i\) 權值為 \(c_k\) 的邊,最後求最短路徑即可。
- 如果要求最小值,則想辦法把每個不等式變為標準 \(x_i-x_j>=c_k\) 的形式,然後建立一條從 \(j\) 到 \(i\) 權值為 \(c_k\) 的邊,最後求最長路徑即可。
基本題
CodeVS 4416 - FFF 團臥底的後宮
給出 n
個形如 $x_i - x_j <= d $ 或 $x_i - x_j >= d $ 的不等式,求一組使 與 差最大的解,輸出最大差值,若無解輸出 -1
,若 與 的差為無限大則輸出 -2
。
#include <cstdio>
#include <climits>
#include <algorithm>
#include <queue>
const int MAXN = 1000;
const int MAXM = 10000;
struct Edge;
struct Node;
struct Node {
Edge *edges;
bool inQueue;
int dist;
int count;
} nodes[MAXN];
struct Edge {
Node *from, *to;
int w;
Edge *next;
Edge(Node *from, Node *to, int w) : from(from), to(to), w(w), next(from->edges) {}
};
int n, m, k;
inline void addEdge(int from, int to, int w) {
nodes[from].edges = new Edge(&nodes[from], &nodes[to], w);
}
inline bool bellmanFord() {
std::queue<Node *> q;
q.push(&nodes[0]);
while (!q.empty()) {
Node *node = q.front();
q.pop();
node->inQueue = false;
for (Edge *edge = node->edges; edge; edge = edge->next) {
if (edge->to->dist > node->dist + edge->w) {
edge->to->dist = node->dist + edge->w;
if (!edge->to->inQueue) {
edge->to->inQueue = true;
edge->to->count++;
q.push(edge->to);
if (edge->to->count > n) {
return false;
}
}
}
}
}
return true;
}
int main() {
scanf("%d %d %d", &n, &m, &k);
for (int i = 0; i < n; i++) {
nodes[i].dist = INT_MAX;
}
nodes[0].dist = 0;
for (int i = 0; i < m; i++) {
int a, b, d;
scanf("%d %d %d", &a, &b, &d);
a--, b--;
addEdge(a, b, d);
// $b - $a <= d
// $a + d >= $b
}
for (int i = 0; i < k; i++) {
int a, b, d;
scanf("%d %d %d", &a, &b, &d);
a--, b--;
addEdge(b, a, -d);
// b - a >= d
// a - b <= -d
// b + -d >= a
}
if (!bellmanFord()) {
puts("-1");
} else {
if (nodes[n - 1].dist == INT_MAX) {
puts("-2");
} else {
printf("%d\n", nodes[n - 1].dist);
}
}
return 0;
}