差分约束
引入
当我们的题目中遇到一些存在不等关系的式子的时候,我们其实可以把它转换成图上问题来解决。为什么呢?我们可以观察一下这两个式子:
\(a_j\le a_i+w\)
\(dis_j\le dis_i+w_{i,j}\)
第一个是我们的要求的式子,第二个是我们在求最短路的时候的式子,是不是可以发现这两个是惊人的相似?那么我们是不是就可以类比一下?
我们把 \(a_i\) 类比成 \(dis_i\) ,\(a_j\) 类比成 \(dis_j\) ,然后我们在跑最短路的时候,一定是会处理出来所有点的 \(dis\) 的,然而对于直接联通的两个点 \(i,j\) 和他们的最短路的值 \(dis_i,dis_j\) 是一定存在某种关系的,而这个关系就是上述的那个式子。
为什么呢?因为从 \(i\) 到 \(j\) 的路径在最短路上要么是 \(w_{i,j}\) 要么就是从其他的地方转移了过去,并且这个距离一定是要比 \(w_{i,j}\) 要小的,要不然也转移不过去。因此,\(dis_j-dis_i\) 一定是小于等于 \(w_{i,j}\) 的(即 \(dis_j-dis_i \le w_{i,j}\)),那么左右移个项,就有了上面的那个式子。又因为我们求得了所有点的最短路,所以说每两个 \(dis\) 之间是一定满足上面的这个要求的,那么类比后的 \(a_j\le a_i+w\) 也就是一定可以成立的了。
同时,我们也不难注意到 \(a_j-a_i\le w \iff a_i-a_j\ge -w\),所以说大于等于和小于等于是本质上是一样的。因此,在求解不等是的问题上,最短路和最长路都可以使用,两种方法并没有本质区别。
在一些题目中,如果说我们每次都从 1 号点出发,可能没有办法到达所有的点,如果是这样的话,我们的最短路也就求不了了,那么我们此时就要建立一个超级源点,使这个源点可以指向所有的点,那么我们从这个点出发,才可以到达其他的所有的点,至于这个点到其他点的边权,那就得看题目怎么要求了,下面会有例子。
差分约束的连边方式
上文我们刚刚说了,可以将不等关系放到图上来做,但是呢,这个毕竟是图,怎么说也是有边权的,那么我们就要看看怎么建边了。
和上面同理,把 \(a_i\) 类比成 \(dis_i\) ,\(a_j\) 类比成 \(dis_j\),基于上面的关系式,我们有证据可以在 \(i\) 和 \(j\) 之间建一条长度为 \(w\) 的边,但是方向怎么确定呢?因为在移项后,是 \(dis_j-dis_i \le w_{i,j}\) 那么就说明 \(j\) 在 \(i\) 之后出现,那么就说明这个边是从 \(i\) 到 \(j\) 的。因此我们就可以这样的建一条边:
cpp
mp[i].push_back({j,w})
当我们每拿到一个不等式组的时候,我们就可以通过这种方法进行建边,又因为我们的 \(w\) 的值不一定是正数,所以说建完图后一般情况下是跑spfa,极少数情况会跑Dijkstra。
但是呢,上面的这种方法不一定是完全正确的,我们稍后会讲,在这之前,我们先把模板题给写了。
P5960 【模板】差分约束
题目描述
给出一组包含 \(m\) 个不等式,有 \(n\) 个未知数的形如:
\(\begin{cases} x_{c_1}-x_{c'1}\leq y_1 \\x{c_2}-x_{c'2} \leq y_2 \\ \cdots\\ x{c_m} - x_{c'_m}\leq y_m\end{cases}\)
的不等式组,求任意一组满足这个不等式组的解。
输入格式
第一行为两个正整数 \(n,m\),代表未知数的数量和不等式的数量。
接下来 \(m\) 行,每行包含三个整数 \(c,c',y\),代表一个不等式 \(x_c-x_{c'}\leq y\)。
输出格式
一行,\(n\) 个数,表示 \(x_1 , x_2 \cdots x_n\) 的一组可行解,如果有多组解,请输出任意一组,无解请输出 NO
。
输入输出样例 #1
输入 #1
3 3
1 2 3
2 3 -2
1 3 1
输出 #1
5 3 5
说明/提示
样例解释
\(\begin{cases}x_1-x_2\leq 3 \\ x_2 - x_3 \leq -2 \\ x_1 - x_3 \leq 1 \end{cases}\)
一种可行的方法是 \(x_1 = 5, x_2 = 3, x_3 = 5\)。
\(\begin{cases}5-3 = 2\leq 3 \\ 3 - 5 = -2 \leq -2 \\ 5 - 5 = 0\leq 1 \end{cases}\)
数据范围
对于 \(100\%\) 的数据,\(1\leq n,m \leq 5\times 10^3\),\(-10^4\leq y\leq 10^4\),\(1\leq c,c'\leq n\),\(c \neq c'\)。
评分策略
你的答案符合该不等式组即可得分,请确保你的答案中的数据在 int
范围内。
如果并没有答案,而你的程序给出了答案,SPJ 会给出 There is no answer, but you gave it
,结果为 WA;
如果并没有答案,而你的程序输出了 NO
,SPJ 会给出 No answer
,结果为 AC;
如果存在答案,而你的答案错误,SPJ 会给出 Wrong answer
,结果为 WA;
如果存在答案,且你的答案正确,SPJ 会给出 The answer is correct
,结果为 AC。
分析
这道题就是非常裸的模板题,按照刚刚上面讲的方法建边就可以了,因为是求合法的解,所以说最短路和最长路没有区别。顺便也把模板给你们写了。
cpp
#include<bits/stdc++.h>
using namespace std;
const int INF=1e4+10,MAXN=1e8;
struct Node{
int p,num;
};
vector<Node> mp[INF];
int n,m;
int dis[INF],used[INF],sum[INF];
void spfa(int x){//spfa模板
queue<int> q;
dis[x]=0,q.push(x),used[x]=1;
while (!q.empty()){
int u=q.front();q.pop();
used[u]=0;
int len=mp[u].size();
for (int i=0;i<len;i++){
int v=mp[u][i].p,w=mp[u][i].num;
if (dis[v]>dis[u]+w){//最短路
dis[v]=dis[u]+w;
if (!used[v]){
if (++sum[v]>=n){//判断有没有负环,具体的之前讲过
cout<<"NO";
exit(0);
}
q.push(v);
used[v]=1;
}
}
}
}
for (int i=1;i<=n;i++){//输出答案
cout<<dis[i]<<" ";
}
return;
}
void fi(){
for (int i=0;i<=n;i++){
dis[i]=MAXN;
}
}
int main(){
cin>>n>>m;
fi();
for (int i=1;i<=m;i++){
int c1,c2,y;
cin>>c1>>c2>>y;
mp[c2].push_back({c1,y});//建边
}
for (int i=1;i<=n;i++){
mp[0].push_back({i,0});//建立超级源点
}
spfa(0);//从0开始跑spfa
return 0;
}
差分约束中的极值问题
极值问题指的是,让我们求出一个满足某个性质的最优解。换句话说就是求某个事件的最大值或最小值(只是一个类比),比如说我们来看下面的这个例子:
假设我们现在要从 \(0 \rightarrow 5 \times 10^4\) 中选出尽量少的数,是每个区间 \([a_i,b_i]\) 内部都有至少 \(c_i\) 个数被选出,我们要怎么做?
你非常敏锐的发现了如果用类似于前缀和的思想来做的话,有这样的一个不等关系式 \(p[b_i]-p[a_i-1]\ge c_i\) 转换一下就可以得到 \(p[a_i-1]-p[b_i]\le -c_i\)
同时脑洞大开的你,又敏锐的发现了相邻两个数之间也存在一定的不等关系式
\(\begin{cases} p[b_{i+1}]-p[b_i]\le1 \\p[b_i]-p[b_{i+1}] \le0\end{cases}\)
经过你一整严密的分析,发现没有其他的式子了,所以说就开始建边。
cpp
mp[bi].push_back({ai-1,-c});
mp[bi].push_back({bi+1,1});
mp[bi+1].push_back({bi,0});
//伪代码
然后你又发现,这道题好像不用建超级源点,所以说直接从最小的编号开始跑最短路,然后输出最大值的答案,这道题就做完了。
ACcode?
cpp
#include<bits/stdc++.h>
using namespace std;
const int INF=2e5+10,MAXN=1e8;
int maxn=-MAXN,minn=MAXN;
struct Node{
int p,num;
};
vector<Node> mp[INF];
int used[INF],dis[INF];
void spfa(int x){
queue<int> q;
dis[x]=0,q.push(x),used[x]=1;
while (!q.empty()){
int u=q.front();q.pop();
used[u]=0;
for (auto v:mp[u]){
if (dis[v.p]>dis[u]+v.num){
dis[v.p]=dis[u]+v.num;
if (!used[v.p]){
q.push(v.p);
used[v.p]=1;
}
}
}
}
}
void fi(){
for (int i=minn;i<=maxn;i++){
dis[i]=MAXN;
}
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
int u,v,w;
cin>>u>>v>>w;
mp[v].push_back({u-1,-w});
maxn=max(maxn,v),minn=min(minn,u-1);
}
fi();
for (int i=minn;i<=maxn;i++){
mp[i+1].push_back({i,0});
mp[i].push_back({i+1,1});
}
spfa(minn);
cout<<dis[maxn];
return 0;
}
当你满怀信心的去测样例的时候,样例会让你直接寄掉。为什么,我们来看看这个样例
5
3 7 3
8 10 3
6 8 1
1 3 1
10 11 1
如果说,我们按照上述代码去做的话,最终的图长成这个样子

手画的长成这样。

真的非常的抽象。
同时如果我们从 0 开始跑最短路的话,就是会直通11点,把每个点都选上,然后这样的答案就是11.
这样肯定是不对的,为什么呢?因为最短路求得的是最大解,求得是极大值!
对于这个问题,其实网络上很多的博客都只是讲述了说什么,最大值用最短路,最小值用最长路,尽管这个结论是正确的,但是我们也要搞清楚为什么可以这样弄。所以说我们下面来证明一下:
对于最短路而言,他的不等关系式通常是 \(x_i - x_j \le a\),那么我们设当前有一条从 \(0\) 到 \(M\) 的最短路,其中经过了 \(v_1,v_2\dots v_k\) 这些节点。
对于这些关系,我们原本的不等式为:
\(\begin{cases} x_{v_1}-x_0 \le a_1 \\x_{v_2}-x_{v_1}\le a_2 \\ \dots\\ x_M-x_{v_k}\le a_{k+1}\end{cases}\)将左侧和右侧分别累加得到 \(x_M-x_0\le\sum_{i=1}^{k+1}a_i\)
又因为这条最短路是确切经过了这些点的,所以说又有
\(\begin{cases}dis_{v_1}-dis_0 =a_1\\dis_{v_2}-dis_{v_1}=a_2\\\dots\\dis_M-dis_{v_k}=a_{k+1}\end{cases}\)这些式子累加后可以得到 \(dis_M-dis_0=\sum_{i=1}^{k+1}a_i\)
不难发现,第二个式子是第一个式子的上界,因此最短路求得的就是最大解。
对于最长路而言,他的不等关系式通常是 \(x_i - x_j \ge a\),那么我们设当前有一条从 \(0\) 到 \(M\) 的最短路,其中经过了 \(v_1,v_2\dots v_k\) 这些节点。对于这些关系,我们原本的不等式为:
\(\begin{cases} x_{v_1}-x_0 \ge a_1 \\x_{v_2}-x_{v_1}\ge a_2 \\ \dots\\ x_M-x_{v_k}\ge a_{k+1}\end{cases}\)将左侧和右侧分别累加得到 \(x_M-x_0\ge\sum_{i=1}^{k+1}a_i\)
又因为这条最短路是确切经过了这些点的,所以说又有
\(\begin{cases}dis_{v_1}-dis_0 =a_1\\dis_{v_2}-dis_{v_1}=a_2\\\dots\\dis_M-dis_{v_k}=a_{k+1}\end{cases}\)这些式子累加后可以得到 \(dis_M-dis_0=\sum_{i=1}^{k+1}a_i\)
不难发现,第二个式子是第一个式子的下界,因此最长路求得的就是最小解。
因此,我们在建边的时候,还要看一下题目中要我们求得是什么,是最大解还是最小解。就比如说上面的那个题,要我们求的就是"选出最少的数",也就是最小解,那么我们现在就应该跑最长路,而最长路和最短路的建图方式又有一点不同,要反着建,所以说这就是我上面所说的"这个建图方法不一定是完全正确的"。
ACcode
cpp
#include<bits/stdc++.h>
using namespace std;
const int INF=2e5+10,MAXN=1e8;
int maxn=-MAXN,minn=MAXN;
struct Node{
int p,num;
};
vector<Node> mp[INF];
int used[INF],dis[INF];
void spfa(int x){
queue<int> q;
dis[x]=0,q.push(x),used[x]=1;
while (!q.empty()){
int u=q.front();q.pop();
used[u]=0;
for (auto v:mp[u]){
if (dis[v.p]<dis[u]+v.num){
dis[v.p]=dis[u]+v.num;
if (!used[v.p]){
q.push(v.p);
used[v.p]=1;
}
}
}
}
}
void fi(){
for (int i=minn;i<=maxn;i++){
dis[i]=-MAXN;
}
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
int u,v,w;
cin>>u>>v>>w;
mp[u-1].push_back({v,w});//反着建
maxn=max(maxn,v),minn=min(minn,u-1);
}
fi();
for (int i=minn;i<=maxn;i++){//反着建
mp[i].push_back({i+1,0});
mp[i+1].push_back({i,-1});
}
spfa(minn);
cout<<dis[maxn];
return 0;
}
插一嘴题外话,这里其实还是可以跑最短路的,因为我们的终点和起点都只是单一点,而不是多对一或者是一对多,因此如果说我们建最短路的话,其实就是从终点跑回起点,然后取个相反数,这样的答案也是正确地。
为什么?原因很简单,对于一个有向图,如果说我们把所有边的方向都取反,同时也将所有权值也取反,那么我们最终的答案也是一定会取反的,这个是非常显然的,我们可以自己举个例子感受感受。
cpp
#include<bits/stdc++.h>
using namespace std;
const int INF=2e5+10,MAXN=1e8;
int maxn=-MAXN,minn=MAXN;
struct Node{
int p,num;
};
vector<Node> mp[INF];
int used[INF],dis[INF];
void spfa(int x){
queue<int> q;
dis[x]=0,q.push(x),used[x]=1;
while (!q.empty()){
int u=q.front();q.pop();
used[u]=0;
for (auto v:mp[u]){
if (dis[v.p]>dis[u]+v.num){
dis[v.p]=dis[u]+v.num;
if (!used[v.p]){
q.push(v.p);
used[v.p]=1;
}
}
}
}
}
void fi(){
for (int i=minn;i<=maxn;i++){
dis[i]=MAXN;
}
}
int main(){
int n;
cin>>n;
for (int i=1;i<=n;i++){
int u,v,w;
cin>>u>>v>>w;
mp[v].push_back({u-1,-w});
maxn=max(maxn,v),minn=min(minn,u-1);
}
fi();
for (int i=minn;i<=maxn;i++){
mp[i+1].push_back({i,0});
mp[i].push_back({i+1,1});
}
spfa(maxn);
cout<<abs(dis[minn]);
return 0;
}
差分约束的小结
对于题目中含有不等关系式的题目,我们通常都可以转换成差分约束来做,但是呢就要注意一下题目中要我们求的东西,是最大值还是最小值,还是其他什么乱七八糟的值,但是都要抓住这个值来看。
如果是最小值,那么就跑最长路,同时将题目中的不等式改为 \(a_j-a_i\ge w\) 的形式,然后从 \(i\) 向 \(j\) 连长度为 \(w\) 的边。
如果是最大值,那么就跑最短路,同时将题目中的不等式改为 \(a_i-a_j\le w\) 的形式,然后从 \(j\) 向 \(i\) 连长度为 \(w\) 的边。
例题感知
这道题在洛谷上已经不能过了,因为被卡SPFA了,但仍然是一道非常经典的差分约束的题。
P3275 [SCOI2011] 糖果
题目描述
幼儿园里有 \(N\) 个小朋友,\(\text{lxhgww}\) 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候,\(\text{lxhgww}\) 需要满足小朋友们的 \(K\) 个要求。幼儿园的糖果总是有限的,\(\text{lxhgww}\) 想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 \(N\),\(K\)。接下来 \(K\) 行,表示这些点需要满足的关系,每行 \(3\) 个数字,\(X\),\(A\),\(B\)。
- 如果 \(X=1\), 表示第 \(A\) 个小朋友分到的糖果必须和第 \(B\) 个小朋友分到的糖果一样多;
- 如果 \(X=2\), 表示第 \(A\) 个小朋友分到的糖果必须少于第 \(B\) 个小朋友分到的糖果;
- 如果 \(X=3\), 表示第 \(A\) 个小朋友分到的糖果必须不少于第 \(B\) 个小朋友分到的糖果;
- 如果 \(X=4\), 表示第 \(A\) 个小朋友分到的糖果必须多于第 \(B\) 个小朋友分到的糖果;
- 如果 \(X=5\), 表示第 \(A\) 个小朋友分到的糖果必须不多于第 \(B\) 个小朋友分到的糖果;
输出格式
输出一行,表示 \(\text{lxhgww}\) 老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 \(-1\)。
输入输出样例 #1
输入 #1
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
输出 #1
11
说明/提示
对于 \(30\%\) 的数据,保证 \(N\leq100\)
对于 \(100\%\) 的数据,保证 \(N\leq100000\)
对于所有的数据,保证 \(K\leq100000, 1\leq X\leq5, 1\leq A, B\leq N\)
分析
根据题目,我们可以分析出来有这么几个关系式
\(\begin{cases} x_i=x_j\\x_i<x_j\\x_i\ge x_j\\x_i>x_j\\x_i\le x_j\end{cases} \iff \begin{cases} x_i-x_j \ge 0且x_j-x_i\le 0\\ x_j-x_i\ge 1\\x_i-x_j\ge0\\x_i-x_j\ge1\\x_j-x_i\ge0\end{cases}\)
再加上这道题求得是最小值,所以说跑最长路,并且把所有的不等式都整理成 \(a_j-a_i \ge w\),如上。然后建边。到这里都是非常简单的,接下来的超级源点就有意思了。我刚刚说了,超级源点到其他点的路径长是由确切的题目所定下来的,并不一定是0,那么这个地方就有变化了。题目中说了,要让每个小朋友都分到糖果,所以说这里超级源点向每个边连的值应该是1而不是0,这个是非常关键的。
同时这道题也是不能跑最短路的,因为这道题是一对多,如果说建成最短路的图的话,你就不知道应该从那个点出发了,除非说你再建一个超级源点,但是这样就太麻烦了。
cpp
#include<bits/stdc++.h>
using namespace std;
const int INF=1e5+10;
struct Node{
int v,w;
};
vector<Node> mp[INF];
int cnt[INF],dis[INF],used[INF];
int n,k;
void spfa(int x){
memset(dis,-0x3f,sizeof(dis));
queue<int> q;
q.push(x),dis[x]=0,cnt[x]=1;
while (!q.empty()){
int u=q.front();q.pop();
used[u]=0;
int len=mp[u].size();
for (int i=0;i<len;i++){
int v=mp[u][i].v,w=mp[u][i].w;
if (dis[v]<dis[u]+w){
dis[v]=dis[u]+w;
if (++cnt[v]>n){
cout<<-1;
return;
}
if (!used[v]){
used[v]=1;
q.push(v);
}
}
}
}
long long tot=0;
for (int i=1;i<=n;i++){
tot+=dis[i];
}
cout<<tot;
}
int main(){
cin>>n>>k;
for (int i=1;i<=k;i++){
int x,a,b;
cin>>x>>a>>b;
if (x==1){
mp[a].push_back({b,0});
mp[b].push_back({a,0});
}else if (x==2){
mp[a].push_back({b,1});
if (a==b){
cout<<-1;
return 0;
}
}else if (x==3){
mp[b].push_back({a,0});
}else if (x==4){
mp[b].push_back({a,1});
if (a==b){
cout<<-1;
return 0;
}
}else mp[a].push_back({b,0});
}
for (int i=1;i<=n;i++){
mp[0].push_back({i,1});
}
spfa(0);
return 0;
}