差分约束

今天我们说一下差分约束。
差分约束是什么呢?差分约束即差分约束系统,

如果一个系统由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;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值