1. 基本概念
差分约束系统(Difference Constraints System)是一类特殊的线性不等式组,形式为:
- xi−xj≤ckx_i - x_j \leq c_kxi−xj≤ck
- xi−xj≥ckx_i - x_j \geq c_kxi−xj≥ck
- xi−xj=ckx_i - x_j = c_kxi−xj=ck
其中 xix_ixi 是变量,ckc_kck 是常数。
2. 核心思想
将不等式转化为图论中的最短路径问题:
- 每个变量 xix_ixi 对应图中的一个节点
- 每个不等式 xi−xj≤ckx_i - x_j \leq c_kxi−xj≤ck 对应一条从 jjj 到 iii 的边,权值为 ckc_kck
- 系统有解 ⇔\Leftrightarrow⇔ 图中无负环
3. 不等式转化规则
| 原始不等式 | 转化形式 | 对应边 |
|---|---|---|
| xi−xj≤cx_i - x_j \leq cxi−xj≤c | xi≤xj+cx_i \leq x_j + cxi≤xj+c | j→cij \xrightarrow{c} ijci |
| xi−xj≥cx_i - x_j \geq cxi−xj≥c | xj≤xi−cx_j \leq x_i - cxj≤xi−c | i→−cji \xrightarrow{-c} ji−cj |
| xi−xj=cx_i - x_j = cxi−xj=c | xi≤xj+cx_i \leq x_j + cxi≤xj+c 且 xj≤xi−cx_j \leq x_i - cxj≤xi−c | j→cij \xrightarrow{c} ijci 和 i→−cji \xrightarrow{-c} ji−cj |
4. 经典问题类型
4.1 求可行解
判断系统是否有解,若有则给出一组解。
4.2 求最大/最小值
- 求 xn−x1x_n - x_1xn−x1 的最大值 ⇒\Rightarrow⇒ 求 x1x_1x1 到 xnx_nxn 的最短路径
- 求 xn−x1x_n - x_1xn−x1 的最小值 ⇒\Rightarrow⇒ 求 x1x_1x1 到 xnx_nxn 的最长路径
5. 算法实现
5.1 Bellman-Ford算法
#include <iostream>
#include <vector>
#include <queue>
#include <climits>
using namespace std;
struct Edge {
int u, v, w;
Edge(int _u, int _v, int _w) : u(_u), v(_v), w(_w) {}
};
class DifferenceConstraints {
private:
int n; // 变量数量
vector<Edge> edges;
vector<int> dist;
vector<int> inQueue;
vector<int> count;
public:
DifferenceConstraints(int _n) : n(_n) {
dist.resize(n + 1, INT_MAX);
inQueue.resize(n + 1, false);
count.resize(n + 1, 0);
}
// 添加约束: x[u] - x[v] <= w
void addConstraint(int u, int v, int w) {
edges.emplace_back(v, u, w); // v -> u, weight = w
}
// 添加约束: x[u] - x[v] >= w
void addConstraintGE(int u, int v, int w) {
edges.emplace_back(u, v, -w); // u -> v, weight = -w
}
// 添加约束: x[u] - x[v] = w
void addConstraintEQ(int u, int v, int w) {
addConstraint(u, v, w);
addConstraint(v, u, -w);
}
// 添加源点,连接到所有点,距离为0
void addSource(int source) {
for (int i = 1; i <= n; i++) {
if (i != source) {
edges.emplace_back(source, i, 0);
}
}
}
// SPFA算法检测负环
bool solve() {
queue<int> q;
int source = 0; // 虚拟源点
// 添加虚拟源点到所有点的边
for (int i = 1; i <= n; i++) {
edges.emplace_back(source, i, 0);
}
dist[source] = 0;
q.push(source);
inQueue[source] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
inQueue[u] = false;
for (const Edge& e : edges) {
if (e.u == u && dist[u] != INT_MAX) {
int v = e.v;
int w = e.w;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!inQueue[v]) {
q.push(v);
inQueue[v] = true;
count[v]++;
// 检测负环
if (count[v] > n) {
return false; // 存在负环,无解
}
}
}
}
}
}
return true; // 无负环,有解
}
// 获取变量值
int getValue(int var) {
return dist[var];
}
// 获取两点间最大差值
int getMaxDifference(int u, int v) {
if (dist[u] == INT_MAX || dist[v] == INT_MAX) return INT_MAX;
return dist[v] - dist[u];
}
};
// 示例使用
int main() {
// 问题:x1 - x2 <= 2, x2 - x3 <= 1, x1 - x3 <= 4
// 求 x1 - x3 的最大值
DifferenceConstraints dc(3);
dc.addConstraint(1, 2, 2); // x1 - x2 <= 2
dc.addConstraint(2, 3, 1); // x2 - x3 <= 1
dc.addConstraint(1, 3, 4); // x1 - x3 <= 4
if (dc.solve()) {
cout << "系统有解" << endl;
cout << "x1 - x3 的最大值: " << dc.getMaxDifference(3, 1) << endl;
// 输出各变量值
for (int i = 1; i <= 3; i++) {
cout << "x" << i << " = " << dc.getValue(i) << endl;
}
} else {
cout << "系统无解(存在负环)" << endl;
}
return 0;
}
5.2 Floyd-Warshall算法(适用于小规模)
// 适用于变量较少的情况
bool solveFloyd(vector<vector<int>>& graph, int n) {
// 初始化距离矩阵
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (graph[i][k] != INT_MAX && graph[k][j] != INT_MAX) {
graph[i][j] = min(graph[i][j], graph[i][k] + graph[k][j]);
}
}
}
}
// 检查对角线是否有负值(负环)
for (int i = 1; i <= n; i++) {
if (graph[i][i] < 0) return false;
}
return true;
}
6. 关键技巧
6.1 虚拟源点
当图不连通时,添加一个虚拟源点 x0x_0x0,并添加 xi−x0≤0x_i - x_0 \leq 0xi−x0≤0 的约束(即 x0→xix_0 \to x_ix0→xi 边权为0)。
6.2 边界处理
- 变量编号通常从1开始
- 初始化距离为无穷大
- 注意整数溢出问题
6.3 负环检测
- Bellman-Ford:松弛n次后还能松弛则有负环
- SPFA:某个点入队超过n次则有负环
- Floyd:对角线出现负值则有负环
7. 复杂度分析
| 算法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| Bellman-Ford | O(nm)O(nm)O(nm) | O(n+m)O(n+m)O(n+m) | 通用 |
| SPFA | O(km)O(km)O(km) | O(n+m)O(n+m)O(n+m) | 稀疏图 |
| Floyd | O(n3)O(n^3)O(n3) | O(n2)O(n^2)O(n2) | 小规模、全源最短路 |
其中 nnn 为变量数,mmm 为约束数,kkk 为常数(通常很小)。
8. 实际应用
8.1 项目进度安排
- 任务开始时间约束
- 最早完成时间计算
8.2 货币兑换套利
- 汇率约束系统
- 检测套利机会(负环)
8.3 电路时序分析
- 信号延迟约束
- 关键路径计算
9. 常见错误
- 不等式方向错误:注意 ≤\leq≤ 和 ≥\geq≥ 的转化
- 忘记添加虚拟源点:导致不连通图无法求解
- 负环检测不完整:未正确实现负环判断
- 数组越界:变量编号与数组索引不匹配
- 初始化问题:距离数组未正确初始化
10. 总结
差分约束是将不等式系统转化为图论问题的经典方法,核心是:
- 正确建立图模型
- 使用最短路径算法求解
- 通过负环检测判断可行性
- 利用最短路径值获得最优解
3207

被折叠的 条评论
为什么被折叠?



