• 差分约束系统


    差分约束系统

    X1 - X2 <= 0
    X1 - X5 <= -1
    X2 - X5 <= 1
    X3 - X1 <= 5
    X4 - X1 <= 4
    X4 - X3 <= -1
    X5 - X3 <= -3
    X5 - X4 <= -3
    不等式组(1) 
        全都是两个未知数的差小于等于某个常数(大于等于也可以,因为左右乘以-1就可以化成小于等于)。这样的不等式组就称作差分约束系统。
        这个不等式组要么无解,要么就有无数组解。因为如果有一组解{X1, X2, ..., Xn}的话,那么对于任何一个常数k,{X1 + k, X2 + k, ..., Xn + k}肯定也是一组解,因为任何两个数同时加一个数之后,它们的差是不变的,那么这个差分约束系统中的所有不等式都不会被破坏。  
        差分约束系统的解法利用到了单源最短路径问题中的三角形不等式。即对于任何一条边u -> v,都有:
    d(v) <= d(u) + w(u, v) 
        其中d(u)和d(v)是从源点分别到点u和点v的最短路径的权值,w(u, v)是边u -> v的权值。
        显然以上不等式就是d(v) - d(u) <= w(u, v)。这个形式正好和差分约束系统中的不等式形式相同。于是我们就可以把一个差分约束系统转化成一张图,每个未知数Xi对应图中的一个顶点Vi,把所有不等式都化成图中的一条边。对于不等式Xi - Xj <= c,把它化成三角形不等式:Xi <= Xj + c,就可以化成边Vj -> Vi,权值为c。最后,我们在这张图上求一次单源最短路径,这些三角形不等式就会全部都满足了,因为它是最短路径问题的基本性质嘛。
        话说回来,所谓单源最短路径,当然要有一个源点,然后再求这个源点到其他所有点的最短路径。那么源点在哪呢?我们不妨自已造一个。以上面的不等式组为例,我们就再新加一个未知数X0。然后对原来的每个未知数都对X0随便加一个不等式(这个不等式当然也要和其它不等式形式相同,即两个未知数的差小于等于某个常数)。我们索性就全都写成Xn - X0 <= 0,于是这个差分约束系统中就多出了下列不等式: 
    X1 - X0 <= 0
    X2 - X0 <= 0
    X3 - X0 <= 0
    X4 - X0 <= 0
    X5 - X0 <= 0
    不等式组(2) 
        对于这5个不等式,也在图中建出相应的边。最后形成的图如下:
    图1 

        图中的每一条边都代表差分约束系统中的一个不等式。现在以V0为源点,求单源最短路径。最终得到的V0到Vn的最短路径长度就是Xn的一个解啦。从图1中可以看到,这组解是{-5, -3, 0, -1, -4}。当然把每个数都加上10也是一组解:{5, 7, 10, 9, 6}。但是这组解只满足不等式组(1),也就是原先的差分约束系统;而不满足不等式组(2),也就是我们后来加上去的那些不等式。当然这是无关紧要的,因为X0本来就是个局外人,是我们后来加上去的,满不满足与X0有关的不等式我们并不在乎。
        也有可能出现无解的情况,也就是从源点到某一个顶点不存在最短路径。也说是图中存在负权的圈。这一点我就不展开了,请自已参看最短路径问题的一些基本定理。
        其实,对于图1来说,它代表的一组解其实是{0, -5, -3, 0, -1, -4},也就是说X0的值也在这组解当中。但是X0的值是无可争议的,既然是以它作为源点求的最短路径,那么源点到它的最短路径长度当然是0了。因此,实际上我们解的这个差分约束系统无形中又存在一个条件:
    X0 = 0 
        也就是说在不等式组(1)、(2)组成的差分约束系统的前提下,再把其中的一个未知数的值定死。这样的情况在实际问题中是很常见的。比如一个问题表面上给出了一些不等式,但还隐藏着一些不等式,比如所有未知数都大于等于0或者都不能超过某个上限之类的。比如上面的不等式组(2)就规定了所有未知数都小于等于0。   
        对于这种有一个未知数定死的差分约束系统,还有一个有趣的性质,那就是通过最短路径算法求出来的一组解当中,所有未知数都达到最大值。下面我来粗略地证明一下,这个证明过程要结合Bellman-Ford算法的过程来说明。
        假设X0是定死的;X1到Xn在满足所有约束的情况下可以取到的最大值分别为M1、M2、……、Mn(当然我们不知道它们的值是多少);解出的源点到每个点的最短路径长度为D1、D2、……、Dn。
        基本的Bellman-Ford算法是一开始初始化D1到Dn都是无穷大。然后检查所有的边对应的三角形不等式,一但发现有不满足三角形不等式的情况,则更新对应的D值。最后求出来的D1到Dn就是源点到每个点的最短路径长度。
        如果我们一开始初始化D1、D2、……、Dn的值分别为M1、M2、……、Mn,则由于它们全都满足三角形不等式(我们刚才已经假设M1到Mn是一组合法的解),则Bellman-Ford算法不会再更新任合D值,则最后得出的解就是M1、M2、……、Mn。
        好了,现在知道了,初始值无穷大时,算出来的是D1、D2、……、Dn;初始值比较小的时候算出来的则是M1、M2、……、Mn。大家用的是同样的算法,同样的计算过程,总不可能初始值大的算出来的结果反而小吧。所以D1、D2、……、Dn就是M1、M2、……、Mn。
        那么如果在一个未知数定死的情况下,要求其它所有未知数的最小值怎么办?只要反过来求最长路径就可以了。最长路径中的三角不等式与最短路径中相反:
    d(v) >= d(u) + w(u, v)
    也就是d(v) - d(u) >= w(u, v) 
        所以建图的时候要先把所有不等式化成大于等于号的。其它各种过程,包括证明为什么解出的是最小值的证法,都完全类似。

    最近几天系统得学习了一些差分约束系统的原理,特此记录如下:
    所谓差分约束系统,是指一组不定方程(A,x,T,b),其中A的每行有一个1,一个-1,其余为0,x为解向量,T为<=或>=组成的向量,b为约束矢量。具体来说,就是每行都具有 xi-xj >=|<= bi 的形式。约束的目标是使得目标函数xt-xs最大或最小。
    这是典型的线性规划的个案,但是也可以转化为图论来做,利用最短路(或最长路)方法可以实现高效的解决方案。


    下面通过poj上的部分例题来详细解释如下:
    poj3159 Candies
    这是我接触差分约束的第一题。设S[a]为kid a获得的candies数,则每一行代表的约束是S[b]-S[a]<=c,目标函数是使得S=S[N]-S[1]最大。
    利用差分约束的思想建图,对于每一条约束,从a向b做一条长为c的边,则从1到N的最短路即为所求。由于本题c皆为非负数,所以可以用Dijkstra高效解决。
    核心代码(构图):
    void input()
    {
    int i,xx,yy,ll;
    scanf("%d %d",&n,&m);
    memset(nodes,-1,(n+1)*sizeof(int));
    for (i=0;i<m;++i)
    {
       scanf("%d %d %d",&xx,&yy,&ll);
       --xx;
       --yy;
       sts[i].py=yy;
       sts[i].pl=ll;
       sts[i].next=nodes[xx];
       nodes[xx]=i;
    }
    }

    poj1364 King
    注意到本题的约束是>和<,然而我们需要的是>=或者<=,所以必须有所转化。幸好是整数规划,所以我们可以转化如下:
    x<a -》 x<=a-1
    x>b -》 x>=b+1
    然而需要注意的是,差分约束必须保证所有的不等式具有同样的不等号才能正确运行!所以我们把所有符号转为<=,然后构图,做Bellman_ford。
    核心代码(构图):
    bool input()
    {
    scanf("%d",&n);
    if (n==0)
       return 0;
    scanf("%d",&m);
    int si,ni,ki,i;
    for (i=0;i<m;++i)
    {
       scanf("%d %d %s %d",&si,&ni,oi,&ki);
       if (oi[0]=='g')
       {
        px[i]=si-1;
        py[i]=si+ni;
        pl[i]=-ki-1;
       }
       else
       {
        px[i]=si+ni;
        py[i]=si-1;
        pl[i]=ki-1;
       }
    }
    return 1;
    }

    poj1716 Integer Intervals
    poj1201 Intervals
    后者是前者的进化版,所以我们以后者为例。
    令S[i]为从0到i-1中数的个数,则以下为约束条件:
    s[bi]-S[ai]>=ci
    1>=S[i+1]-S[i]>=0
    注意本题要求的是最小值,而按照>=号建图后发现图中有负环,怎么办呢?
    其实很简单,本题求的不是最短路,而是最长路!而Bellman_ford依然可以承担此项重任!
    核心代码(构图):
    void input()
    {
    int xx,yy,cc,i;
    scanf("%d",&n);
    st=10000;
    ed=0;
    m=0;
    for (i=0;i<n;++i)
    {
       scanf("%d %d %d",&xx,&yy,&cc);
       px[m]=xx;
       py[m]=yy+1;
       pl[m]=cc;
       if (st>xx)
        st=xx;
       if (ed<yy+1)
        ed=yy+1;
       ++m;
    }
    for (i=st;i<ed;++i)
    {
       px[m]=i;
       py[m]=i+1;
       pl[m]=0;
       ++m;
    }
    for (i=ed;i>st;--i)
    {
       px[m]=i;
       py[m]=i-1;
       pl[m]=-1;
       ++m;
    }
    for (i=st;i<=ed;++i)
       dps[i]=MAXS;
    dps[ed]=0;
    }

    poj3169 Layout
    本题是求约束下的最大值,只要依<=建图并求最短路即可!
    核心代码(构图):
    void input()
    {
    scanf("%d %d %d",&n,&ml,&md);
    m=0;
    int i,xx,yy,ll;
    for (i=0;i<ml;++i)
    {
       scanf("%d %d %d",&xx,&yy,&ll);
       --xx;
       --yy;
       px[m]=xx;
       py[m]=yy;
       pl[m]=ll;
       ++m;
    }
    for (i=0;i<md;++i)
    {
       scanf("%d %d %d",&xx,&yy,&ll);
       --xx;
       --yy;
       px[m]=yy;
       py[m]=xx;
       pl[m]=-ll;
       ++m;
    }
    for (i=1;i<n;++i)
    {
       px[m]=i;
       py[m]=i-1;
       pl[m]=0;
       ++m;
    }
    }

    poj1275 Cashier Employment
    这是我很久以前就从黑书上见到的题目,但是因为当时自己才疏学浅,怎么都写不出来。现在搞懂了差分约束,发现其实不是那么难的!
    利用差分约束寻找可行解,在所有可行解中二分最优。
    核心代码(构图):
    void createmap(int sum)
    {
    int i,j;
    m=0;
    for (i=1;i<25;++i)
    {
       px[m]=i-1;
       py[m]=i;
       pl[m]=0;
       ++m;
    }
    for (i=1;i<25;++i)
    {
       px[m]=i;
       py[m]=i-1;
       pl[m]=-t[i];
       ++m;
    }
    px[m]=0;
    py[m]=24;
    pl[m]=sum;
    ++m;
    for (j=1;j<25;++j)
    {
       i=(j-1+8)$+1;
       px[m]=j;
       py[m]=i;
       if (i>j)
        pl[m]=r[i];
       else
        pl[m]=r[i]-sum;
       ++m;
    }
    }

    总论:
    感谢各位大牛小牛们能耐心看到这里,在此我准备用一些简单的语句来总结差分约束系统的应用特性:
    >=,求最小值,做最长路;
    <=,求最大值,做最短路。
    边都是从后往前~~~
    <=构图。
    有负环说明无解。
    求不出最短路(为Inf)为任意解。
    >=构图时类似。

    以上为我的一点短见,希望各位大牛不吝批评指正!!!

  • 相关阅读:
    struts2的核心和工作原理
    Java操作redis【二十】
    优化【十九】
    管线【十八】
    服务器管理【十七】
    虚拟内存【十六】
    Eclipse:使用findBugs预先检测错误
    linux命令学习(1)
    Android中Linux suspend/resume流程
    Ubuntu12.04下eclipse提示框黑色背景色的修改方法
  • 原文地址:https://www.cnblogs.com/stepping/p/5777277.html
Copyright © 2020-2023  润新知