今天我们说一下差分约束。
差分约束是什么呢?差分约束即差分约束系统,
如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj≤k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。(摘自百度百科)
举个例子,现在有五个变量v0,v1,v2,v3,v4,四个差分约束条件
v0-v1<=10
v1-v2<=9
v2-v3<=8
v3-v4<=7
我们就称这样的为5个变量4个差分约束条件的差分约束系统
一般的问题要求我们解决这样的问题,求最大值,最小值,或者整个差分约束系统是否有解。
那么我们应该如何解决这些问题呢?
以求最大值为例,我们来一步一步分析。
假设现在有一个 3 变量 3 差分约束条件的差分约束系统(如下)。
(1) v1-v0<=3
(2) v2-v1<=4
(3) v2-v0<=10
很明显 (1)+(2) --> v2-v0<=7 这更新了 v2-v0 (因为7比10小)。
我们将上面的差分约束条件转换为点与边,变量相当于点,v1-v0<=3相当于从 v0 指向 v1 的长度为 3 的边。(剩下两个条件类似)
很明显,但我们把差分约束条件转化为图时,v2-v0的最大值,就是v0到v2的最短路,因为我们把 (1) v1-v0<=3 转化为 v0 指向 v1 的长为 3 的边 ,把(2) v2-v1<=4转化为 v1 指向 v2 的长为 4 的边。(1)+(2)就能推出 v2-v0<=7 这就像相当于在图上连了一条从 v0 指向 v2 的长为 7 的边。
因此当我们求某个差分约束条件的最大值就是求图上的最短路,与之类似当我们求某个差分约束条件的最小值就是求图上的最长路,我们求差分约束系统是否有解一般判断图上是否有环。
简单归纳一下如何解决差分约束的问题
1.求最大值
将所有条件转换成 x-y<=z 的形式 如果有 x-y>=z 就转换成 y-x<=-z 如果有 x-y<z 在都是整数的情况下转换成 x-y<=z-1,接下来连边,求最短路即可。
2.求最小值
同上,只不过是把所有条件转换成 x-y>=z 然后连边,求最长路即可。
3.判断是否有解
一般判环即可(因为环可以无限加),spfa中一个点入队次数超过n即有环。
例题
我们来看一个求最小值的题目,poj1201 intervals
题意:有 n 个区间 [ ai,bi ] ,还有一个集合 Z,规定 Z 中必须有不小于确定数量的元素在每个区间中,即区间 [ ai,bi ] 至少有 ci 个元素属于 Z。要求集合 Z 中的元素尽量少,问最少有多少元素?
初看可能发现不了差分约束条件,但是我们可以转化一下,我们规定 d[i] 代表 区间 [ 0,i ] 上有 d[i] 个元素属于集合 Z。那么 区间 [ ai,bi ] 就有 d[bi]-d[ai-1] 个元素属于集合Z。即 d[bi]-d[ai-1]>=ci。 并且 0<=d[i]-d[i-1]<=1,即 d[i]-d[i-1]>=0,d[i-1]-d[i]>=-1。由于数组没有 d[-1],所有我们重新定义 d[i+1]代表区间 [ 0,i ]有 d[i+1]个元素属于集合 Z。后面与此类似 i 都由 i+1 替换。当我们把所有约束条件找完,我们构造图,d[i]-d[j]>=ci,即是一条从 j 指向 i 的长为ci的边。之后用spfa找最长路(最小编号到最大编号的最长路)即可。
代码如下
#include <iostream>
#include <stdio.h>
#include <queue>
using namespace std;
const int maxn=5e4+5;
const int inf=0x7fffffff;
int dis[maxn];
int n,mi,ma;
int head[maxn];
bool vis[maxn];
struct node
{
int to,next,value;
}p[3*maxn];
queue<int>q;
int cnt;
void add(int x,int y,int w)
{
p[++cnt].to=y;
p[cnt].next=head[x];
p[cnt].value=w;
head[x]=cnt;
}
int spfa()
{
fill(dis+mi+1,dis+ma+1,-inf);
q.push(mi);
vis[mi]=true;
while(!q.empty())
{
int temp=q.front();
q.pop();
for(int i=head[temp];i;i=p[i].next)
{
int v=p[i].to;
int c=p[i].value;
if(dis[v]<dis[temp]+c)
{
dis[v]=dis[temp]+c;
if(!vis[v])
q.push(v),vis[v]=true;
}
}
vis[temp]=false;
}
return dis[ma];
}
int main()
{
scanf("%d",&n);
mi=inf,ma=-inf;
for(int i=1;i<=n;i++)
{
int x,y,c;
scanf("%d %d %d",&x,&y,&c);
add(x,y+1,c);
mi=min(mi,x);
ma=max(ma,y+1);
}
//cout<<mi<<" "<<ma<<endl;
for(int i=mi;i<ma;i++)
{
add(i,i+1,0);
add(i+1,i,-1);
}
printf("%d\n",spfa());
return 0;
}