【学习笔记】 差分约束

前置知识

  • 最短路/最长路
  • 判负环

形式化描述

给出一个含 m m m 个不等式的不等式组,形如:

{ x i 1 − x j 1 ≤ k 1 x i 2 − x j 2 ≤ k 2 ⋯ x i m − x j m ≤ k m \begin{cases} x_{i_1}-x_{j_1}\leq k_1 \\x_{i_2}-x_{j_2} \leq k_2 \\ \cdots\\ x_{i_m} - x_{j_m}\leq k_m\end{cases} xi1xj1k1xi2xj2k2ximxjmkm

求出任意一组解 $ a_1=x_1,a_2=x_2,… a_n=x_n $,使得这组解满足这个不等式组。

也可以看这里

讲解

左边所有的式子保持减法,和差分相似;一个不等式,称为一个约束条件。因此,这类题目我们称之为差分约束。

乍一看,好像只能使用暴力来解决这道题(通过不断枚举 x i x_i xi 来找到正确的解),但很显然,暴力的复杂度太高了,当 y y y 变大的时候,暴力所需要的时间就越多。

既然题目不符合暴力,二分,dp等算法或思想的要求,我们来尝试使用图论解决它。

使用图论的前提,就是需要建图。我们观察这些式子并与一些图论相关的算法相比较,显然我们能够发现最短路算法中的松弛条件 $ if(dis[v]>dis[u]+w) $ 与该不等式极其相似。我们将松弛条件变化一下,得到:

d i s [ v ] ≤ d i s [ u ] + w dis[v] \leq dis[u] + w dis[v]dis[u]+w

在最短路中,它的正确性是毋庸置疑的。

将不等式组中的式子变化,得到:

x i ≤ x j + k x_i \leq x_j + k xixj+k

此时, i i i 对应 v v v j j j 对应 u u u k k k 对应 w w w。从 j j j i i i 建一条长度为 k k k 的边,就完成了我们的建图。至此,我们成功的将差分约束问题转化成了最短路问题

(真佩服想到这个的人)

接下来就是跑最短路,如果存在负环,说明这个不等式组是无解的。否则, d i s dis dis 数组中的值就是一组解。

一些说明

  1. 注意到,如果 { a 1 , a 2 , … , a n } \{a_1,a_2,\dots,a_n\} {a1,a2,,an} 是该差分约束系统的一组解,那么对于任意的常数 d, { a 1 + d , a 2 + d , … , a n + d } \{a_1+d,a_2+d,\dots,a_n+d\} {a1+d,a2+d,,an+d} 显然也是该差分约束系统的一组解,因为这样做差后 d 刚好被消掉。

    (oiwiki讲的很清楚,就直接贴上去了)

  2. 如果不等式全是 ≥ \geq 号(求最小距离),此时需要跑最长路;如果不等式全是 ≤ \leq 号(求最大距离),则需要跑最短路。

    我们可以从公式的角度来理解:满足 ≤ \leq 的公式与 d i s [ u ] ≤ d i s [ v ] + w dis[u]\leq dis[v]+w dis[u]dis[v]+w 相似,而 d i s [ u ] ≤ d i s [ v ] + w dis[u]\leq dis[v]+w dis[u]dis[v]+w 这个式子对应的是最短路。反之,满足 ≥ \geq 的公式与 d i s [ u ] ≥ d i s [ v ] + w dis[u]\geq dis[v]+w dis[u]dis[v]+w 相似,而 d i s [ u ] ≥ d i s [ v ] + w dis[u]\geq dis[v]+w dis[u]dis[v]+w 这个式子对应的是最长路。

  3. 求最远距离用最短路,求最近距离用最长路。

    这个点我们可以感性理解一下:求最长距离,数学公式中为 $ dis \leq x $ ,此时是小于号,即最长距离对应的是最短路。同理我们也可以得出最短距离对应的是最短路。

  4. 判负环需的前提是整张图联通,因此我们需要建立一个超级源点保证我们能走到图上的每一个点。

超级源点:到图上的每一点距离为0

总结

差分约束基本步骤:

统一符号(大于号或者小于号) -> 建图 -> 跑 SPFA 求出通解。

这里需要注意有些题可能需要先判断图是否联通,所以可能需要跑两次 SPFA。

总的来说,只要理解了原理,做起题目来就和做入门题没什么区别。

模板见下。

例题

  1. P5960 【模板】差分约束

这题就是把差分约束的最基础的东西抽象出来了。根据不等式建图跑 SPFA 即可。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e6+10;
const int inf = 1e9+7; 

struct edge {
	int from,to,next,w;
}e[N];

int n,m,k,l,r,ans,cnt;

int head[N],dis[N],vis[N],ti[N]; //ti:记录每个点被经过的次数

void add(int u,int v,int w) {
	e[++cnt].next=head[u];
	head[u]=cnt;
	e[cnt].from=u; e[cnt].to=v; e[cnt].w=w;
}

bool spfa(int s) {
	for(int i=0;i<=n;++i) dis[i]=inf;
	dis[s]=0; vis[s]=1;
	queue<int> q;
	q.push(s);
	while(!q.empty()) {
		int u=q.front(); q.pop();
		vis[u]=0;
		for(int i=head[u];i;i=e[i].next) {
			int v=e[i].to; //cout<<"u= "<<u<<" v= "<<v<<"\n";
			if(dis[v]>dis[u]+e[i].w) {
				dis[v]=dis[u]+e[i].w;
				if(!vis[v]) {
					ti[v]++;
					if(ti[v]>=n+1) return 0; 
					q.push(v); vis[v]=1;
				}
			}
		}
	}
	return 1;
}

signed main()
{
	ios::sync_with_stdio(0);
	//memset(head,-1,sizeof(head));
	cin>>n>>m;
	for(int i=1;i<=n;++i) add(n+1,i,0);
	for(int i=1,u,v,w;i<=m;++i) {
		cin>>u>>v>>w;
		add(v,u,w);
	} 
	int ret=spfa(n+1);
	if(ret) {
		for(int i=1;i<=n;++i) cout<<dis[i]<<" "; 
	}
	else cout<<"NO\n";
	return 0;
}
  1. P4878 [USACO] Layout G

这题需要注意隐含条件:

  • 开始时,奶牛们按照编号顺序来排队(暗示 i<j)
  • 可能有多头奶牛在同一位置上(暗示需要给所有相邻的牛建立一条从 i i i i − 1 i-1 i1,长度为 0 0 0 的边)
  • 这题需要 2 2 2 SPFA。(第一次确认是否存在可行解,从超级源点开始跑;第二次确认是否能从 1 1 1 到达 n n n,从 1 1 1 开始跑)
  1. P1993 小K的农场

这题需要注意:当农场 a 的作物与农场 b 的作物数量一致时,需要在 ab 之间建立双向边

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值