差分约束学习笔记

差分约束系统学习笔记(附洛谷 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\) 约束?

两种方法:

  1. 转化为 \(\le\) 形式

    \[x_i - x_j \ge c \iff x_j - x_i \le -c \]

    然后建边 \(i \to j\),边权为 \(-c\)

  2. 直接跑最长路

    • 建边 \(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] 倍杀测量者 对数转换 + 二分答案
posted @ 2026-06-23 09:53  词印  阅读(18)  评论(0)    收藏  举报