差分约束学习笔记
差分约束系统学习笔记(附洛谷 P5960 模板题解)
差分约束是图论中一个非常巧妙的应用,它将求解不等式组的问题转化为求解单源最短路(或最长路)的问题,而 SPFA 算法中的负环检测则对应了不等式组无解的情况。
1. 什么是差分约束?
差分约束系统 是由若干个形如 \(x_i - x_j \le c_k\) 的不等式组成的系统,其中每个不等式的形式都是两个变量的差小于等于一个常数。
求解差分约束系统,就是找一组实数 \(\{x_1, x_2, ..., x_n\}\),使得所有不等式同时成立。
2. 核心思想:不等式转图论
对于不等式:
\(x_i - x_j \le c\)
我们将其移项,得到:
\(x_i \le x_j + c\)
这和图论中最短路的松弛条件:
\(dist[i] \le dist[j] + w(j, i)\)
在形式上完全一致。
因此,我们可以建立如下映射:
| 差分约束 | 图论 |
|---|---|
| 变量 \(x_i\) | 节点 \(i\) |
| 不等式 \(x_i - x_j \le c\) | 有向边 \(j \to i\),边权为 \(c\) |
| 求解一组可行解 | 求单源最短路(\(dist\) 数组即为答案) |
| 不等式组无解 | 图中存在负环 |
3. 代码实现详解(基于 P5960 模板)
3.1 题目背景
洛谷 P5960 【模板】差分约束 是差分约束的模板题,题目给定 \(n\) 个变量和 \(m\) 个形如 \(x_c - x_{c1} \le y\) 的不等式,要求输出任意一组可行解。
3.2 完整代码
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 5e3+10;
struct edge {int to,w;};
int n,m;
int c,c1,y;
int dis[N],vis[N],cnt[N];
vector<edge> e[N];
bool spfa(){
memset(dis,0x3f,sizeof dis);
dis[0]=0,vis[0]=1;
queue<int> q; q.push(0);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(auto i : e[u]){
int v=i.to;
if(dis[v]>dis[u]+i.w){
dis[v]=dis[u]+i.w;
if(!vis[v]){
vis[v]=1;
cnt[v]=cnt[u]+1;
if(cnt[v]==n+1) return false;
q.push(v);
}
}
}
}
return true;
}
int main(){
cin.tie(0)->sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>c>>c1>>y;
e[c1].pb({c,y});
}
for(int i=1;i<=n;i++){
e[0].pb({i,0});
}
if(!spfa()) cout<<"NO\n";
else {
for(int i=1;i<=n;i++) cout<<dis[i]<<" ";
}
return 0;
}
3.3 关键代码解释
(1) 为什么超级源点的边权是 0?
- 边 \(0 \to i\) 权值为 \(0\),对应约束 \(x_i - x_0 \le 0\)。
- 令 \(x_0 = 0\),则等价于 \(x_i \le 0\)。
- 这相当于给出了变量的一组初始上界,保证了图是连通的,所有节点都能从源点到达。
- 超级源点的加入不会影响不等式的可解性,只会让解集中所有变量都有上界 \(0\)。
(2) 负环检测的逻辑
- 在 SPFA 中,\(cnt[v]\) 表示节点 \(v\) 入队的次数。
- 根据 SPFA 的性质,如果不存在负环,每个节点最多入队 \(n\) 次。
- 如果 \(cnt[v] > n\)(即代码中的 \(cnt[v] = n + 1\)),说明存在负环,不等式组无解。
(3) 为什么用 \(cnt[v] = cnt[u] + 1\)?
- 这是基于路径长度的计数方式。
- \(cnt[v]\) 表示从源点到 \(v\) 的最短路径经过的边数。
- 如果最短路径经过了 \(n+1\) 条边,说明路径上必然有重复节点,即存在负环。
- 这种写法比单纯记录入队次数更优雅,也更贴合 SPFA 的"最多松弛 \(n-1\) 轮"的理论。
4. 算法要点与负环判断
4.1 最短路转化最长路
- 如果题目约束是 \(x_i - x_j \le c\),求最大值或任意解 \(\to\) 跑最短路(检测负环)。
- 如果题目约束是 \(x_i - x_j \ge c\),求最小值 \(\to\) 跑最长路(检测正环)。
4.2 如何处理 \(\ge\) 约束?
两种方法:
-
转化为 \(\le\) 形式:
\[x_i - x_j \ge c \iff x_j - x_i \le -c \]然后建边 \(i \to j\),边权为 \(-c\)。
-
直接跑最长路:
- 建边 \(j \to i\),边权为 \(c\)。
- 松弛条件改为
if (dis[v] < dis[u] + w)。 - 检测正环(如果某节点入队 \(n+1\) 次,存在正环,无解)。
4.3 严格不等式 \(<\)
如果变量是整数,\(x_i - x_j < c\) 等价于 \(x_i - x_j \le c - 1\)。如果变量是实数,差分约束无法处理严格不等式。
4.4 等式 \(=\)
拆成两个不等式:
\[x_i - x_j = c \iff x_i - x_j \le c \quad \text{且} \quad x_j - x_i \le -c
\]
5. 常见变形与应用场景
| 应用场景 | 核心建模技巧 |
|---|---|
| 区间选点问题(如种树、区间选最少点) | 定义前缀和 \(S[i]\),用区间 \([l, r]\) 和 \(c\) 构造 \(S[r] - S[l-1] \ge c\) |
| 判断账本是否矛盾(如狡猾的商人) | 前缀和建图,判断是否存在负环 |
| 变量有上下界 | 超级源点边权对应上界,节点到源点边权对应下界 |
| 乘积约束(如倍杀测量者) | 取对数转化为线性差分约束 |
6. 总结与练习
6.1 解题流程
1. 识别变量,列出所有不等式(包括隐含条件)
2. 确定求最大值(最短路)还是最小值(最长路)
3. 建图:不等式 -> 有向边
4. 添加超级源点(通常边权为 0)
5. 运行 SPFA,检测负环(或正环)
6. 有环 -> 无解;无环 -> dis 数组即为可行解
6.2 习题
| 阶段 | 题目 | 说明 |
|---|---|---|
| 1 | P5960 【模板】差分约束 | 纯模板,熟悉建图和 SPFA |
| 2 | P1993 小K的农场 | 同时包含 \(\le\) 和 \(\ge\),判断无解 |
| 3 | P3275 [SCOI2011] 糖果 | 求最小值 \(\to\) 最长路,经典题 |
| 4 | P1250 种树 | 前缀和建模,区间选点问题 |
| 5 | P2294 [HNOI2005] 狡猾的商人 | 前缀和判断矛盾 |
| 6 | P4926 [1007] 倍杀测量者 | 对数转换 + 二分答案 |

浙公网安备 33010602011771号