• 4. 差分约束系统


    1.定义:

      如果一个系统由n个变量和m个约束条件组成,形成m个形如ai-aj≤k的不等式(i,j∈[1,n],k为常数),则称其为差分约束系统(system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。

      求解差分约束系统,可以转化成图论的单源最短路径(或最长路径)问题。

    2. 数形结合

       若一个系统由 n 个变量和 m 个不等式组成,并且这 m 个不等式对应的系数矩阵中每一行有且仅有一个 1 或 -1 ,其它的都为 0,这样的系统称为差分约束(difference constraints)系统。例如:

                     

      观察 x [ i ]  - x [ j ] <= a [ k ], 将 x [ j ] 移至不等号的右边,即 x [ i ]  <= x [ j ] + a [ k ].再进行同等的形式变换 d [ u ] + w (u, v) >= d [ v ]. 这时候联想到 SPFA 的松弛操作:

     if (d[u] + w(u, v) < d[v) {
         d[v] = d[u] + w(u, v);   
    }

      对比上面的两个不等式,发现两个不等式的不等号正好相反,但思考一下其实它们的逻辑是一致的,这是因为SPFA的松弛操作是在满足小于的情况下进行的松弛,最后力求达到 d [ u ] + w (u, v) >= d [ v ] 的效果。所以我们可以将每个不等式转化为图上的有向边。

      对于每个不等式 x [ i ]  - x [ j ] <= a [ k ], 从结点 j 到 i 建立一条 j -> i 的有向边,边的权值为  a [ k ], 求 x [ n - 1 ] - x [ 0 ] 的最大值就是求 0 -- n - 1 的最短路。

              

    3. 解的存在性

      在最短路的问题中,会出现负圈或者不可达的情况,所以在不等式转化到图上时也可能出现上述问题。如果路径中出现了负圈,则表示最短路无限小,即不存在最短路,那么 x [ t ] - x [ s ] <= T , T 无限小, 那么 x [ t ] - x [ s ] 的最大值不存在。

        

      如果是从起点 s 无法到达 t 的情况,则表明 x [ t ] 和 x [ s ] 之间并没有约束条件,这种情况下  x [ t ] - x[ s ] 的最大值是无限大,解有无穷个。

        

      综上所述:差分约束系统的解有三种情况:有解, 有无穷多解,无解。

    4. 最大值 --> 最小值

       当我们把上面的不等式组中 “ <= ” 全部变为 " >= ", 则就变为求 0 -- n - 1 的最长路。

    5. 不等式的标准化

      首先统一不等号的方向,如果求最大值,将不等号全部变为 “ <= ”,建图后求最短路;

                 如果求最小值,将不等号全部变为 " >= ",建图后求最长路。

      如果有形如  A - B = C 这样的等式,则将其转化为:

            A - B >= C

            A - B <= C

      再将其中的一种不等号反向,建图即可。

      如果变量都是定义在整数域上, 那么遇到 A - B < C 这样的不带等号的不等式, 我们需要将其转化为 “ <= ” 或 “ >= ”,  即 A - B <= C - 1。

    6. 差分约束的经典应用

      (1) 线性约束

        线性约束一般是在一维空间中给出一些变量(一般是定义位置),然后告诉你某两个变量的约束关系求两个变量 a 和 b 的差值的最大值或最小值。

       例题: N 个 人编号为 1 - N,并按照编号排成一列,任何两个人的位置不重合。给定一些约束条件:

             X 组约束条件 A和 Bx 的距离不大于 C

             Y组约束条件 Ay 和 By 的距离不小于 Cy

        如果这样的排列存在,求1 - N 的最长可能距离,如果不存在输出 -1,如果无限长输出 -2.

      令 d [ x ] 为第 x 个人的位置,根据题意有如下的约束条件:

             对于X组,有d[ Bx  ] - d[ Ax ] <= Cx;

             对于Y组,有d[ By ] - d[ Ay ] >= Cy;

             任意两人位置不重合,有  d[ x ] - d [ x - 1] >= 1

      我们要求的是 d[ N ] - d[ 1 ] 的最大值,即表示为 d[ N ] - d[ 1 ] <= T. 将所有的不等式转化为 “  <= ” 的形式:

             d[ Bx  ] - d[ Ax ] <= Cx;

             d[ Ay ] - d[ By ] <= -Cy;

             d[ x - 1 ] - d[ x ] <= -1;

      对于 d[ x ] - d[ y ] <= z ,令 z = w(x, y),那么有 d[ x ] <= d[ y ] +  w(x, y) ,所以当 d[ x ] > d[ y ] + w(x, y)时,更新 d[ x ] 的值,这就对应了最短路的松弛操作,于是问题就转化成了求 1 到 N 的最短路。

      对于所有不等式  d[ x ] - d[ y ] <= z,从 y 向 x 建立一条权值为 z 的有向边。然后从 1 出发,利用SPFA求到各个点的最短路,如果 1 到 N 不可达,说明最短路无限长; 如果某个点进入队列的次数 >= N 次,则必定存在负圈,即没有最短路,否则 最后的的 d[ N ] 就是最短路。

       (2) 区间约束

      例题:POJ-1201 Intervals

      给定 n 个整数闭区间 和 每个区间中至少有多少整数需要被选中,每个区间的范围为 [ a, b ],(0 <= a <= b <= 50000) 并且至少 c 个点被选中, 求 [0, 50000] 中至少需要多少点被选中。例如 : 3 6 2 表示[ 3, 6]区间里至少选择 2 个点,有C24种情况。

      这类问题没有线性约束那么明显,需要将问题转化一下。考虑到最后需要求的是一个完整区间内至少有多少点被选中,我们试着用 d [ i ] 表示 [0, i]区间至少有多少点能被选中,显然 d[ - 1 ] = 0.对于每个区间描述,可以表示成 d[ b ] - d[ a ] >= c,即 d[ b ] >= d[ a ] + c, 而我们的目标是 d[ 50000 ] - d[ - 1 ] >= T 这个不等式中的 T,将所有区间描述转化成图后求 -1 到 50000 的最长路。

       这里忽略了一些要素,因为 d[ i ] 描述了一个求和函数,所以对于 d[ i ] 和 d[ i - 1 ] 是有限制的。考虑到每个点都有选和不选两种情况,所以 d[ i ] 和 d[ i - 1 ] 需要满足  0 <= d[ i ] - d[i - 1] <=  1 (即第 i 个数是选或不选)。

       这样一来,还需要加入 50000 * 2 = 100000 条边,由于数据较大需要用 SPFA 优化,由于 -1 不能映射到小标,所以可以将所有点右移一位。

      (3) 未知条件约束

        未知条件约束是指在不等式的右边不一定是一个常数,可能是个未知数,通过枚举这个未知数,然后对不等式转化为差分约束进行求解。

      例题:POJ-1275 Cashier Employment

       在一家超市里,每个时刻都需要有营业员看管, R(i) (0 <= i <= 24)表示从 i 时刻开始到 i + 1 时刻结束需要的营业员的数目,现在有 N 个人申请这项工作,并且每个申请者都有一个起始工作时间 ti ,如果第 i 个申请者被录用,那么他会连续工作 8 小时。现在要求选择一些申请者录用,使得任一时刻 i ,营业员数目都能 ≥ R(i)。

      i = 0,1,...,23,分别对应时刻[i, i + 1],特别的,23 表示的是[23, 0],并且有些申请者的工作时间可能会跨天。

      a[ i ] 表示第 i 时刻开始工作的人数 -- 未知量

      b[ i ]表示第 i 时刻能够开始工作的人数上限 -- 已知量

      R[ i ]表示第 i 时刻必须值班的人数 -- 已知量

      那么第 i 时刻到第 i + 1 时刻还在工作的人满足下面两个不等式(每人工作8小时)

        a[i - 7] + a[ i - 6] +  ... + a[ i ] ≥ R[ i ]                      i ≥ 7

        (a[ 0 ] + ... + a[ i ]) + (a[ i + 17 ] + ... + a[ 23 ])  ≥ R[ i ]    0 ≤ i < 7

      对于从第 i 时刻开始工作的人,满足以下不等式:

        0 ≤ a[ i ] ≤ b[ i ]      0 ≤ i < 24

      令 s[ i ] = a[ 0 ] + .. + a[ i ],特别的,s[ - 1 ] = 0,上面三个式子用 s[ i ] 来表示,如下:

        s[ i ] - s[ i - 8 ] ≥ R[ i ]        i ≥ 7       (1)

        s[ i ] + s[ 23 ] - s[ i + 16 ] ≥ R[ i ]           0 ≤ i < 7      (2)  

        0 ≤ s[ i ] - s[ i - 1 ] ≤ b[ i ]       0 ≤ i < 24              (3)

      仔细观察不等式(2),有三个未知数,这里的 s[ 23 ] 就是未知量,所以还无法转化成差分约束求解,但是和 i 相关的变量只有两个,对于s[ 23 ]的值可以进行枚举,令 s[ 23 ] = T, 则有以下几个不等式:

        s[ i ] - s[ i - 8 ] ≥ R[ i ]

        s[ i ] - s[ i + 16 ] ≥ R[ i ] - T

        s[ i ] - s[ i - 1 ] ≥ 0

        s[ i - 1 ] - s[ i ] ≥ -b[ i ]

      对于所有的不等式 s[ y ] - s[ x ] ≥ c,建立一条权值为 c 的边 x -> y,于是问题就转化成了求从原点 -1 到终点 23 的最长路。

      我们还少了一个条件,即 s[ 23 ] = T,我们需要把它转化为不等式,由于设定 s[ -1 ] = 0,所以 s[ 23 ] - s[ -1 ] = T,转化为不等式:

        s[ 23 ] - s[ 1 ] ≥ T

        s[ -1 ] - s[ 23 ] ≥ -T

      将这两条边补到原图中,求出的最长路 s[ 23 ] = T,表示 T 就是满足条件的一个解,由于 T 的值是从小到大枚举的 (0 ≤ T ≤ N),所以第一个满足条件的解就是答案。

      最后观察申请者的数量,当 i 个申请者能够满足条件的时候, i + 1 个申请者必定可以满足条件,所以申请者的数量是单调的,可以对 T 进行二分枚举,将枚举复杂度从 0(N)降为 0(log N)

      补题:

      1. Layout( POJ 3169 )

                        

      很明显的差分约束,所要求的是 N 到 1 的最大距离,即  d[ N ] - d[ 1 ] ≤ T, 根据题意得出不等式:

        BL -  AL ≤ DL

        AD - BD ≤ DD

      转化为求图的最短路

      

    int N, ML, MD;
    int AL[MAX_ML], BL[MAX_ML], DL[MAX_ML];
    int AD[MAX_MD], BD[MAX_MD], DD[MAX_MD];
    
    int d[MAX_N];
    
    //SPFA
    typedef pair<int, int> P;
    struct edge{
        int to;
        int cost;
    };
    vector<edge> G[MAX_N];
    //int d[MAX_N]; 
    int vis[MAX_N];        //被访问的次数 
    bool inq[MAX_V];    //结点 i 是否在队列中 
    
    void Bellman_Ford() {
        fill(d, d + N, INF);    
        d[0] = 0;
        for (int k = 0; k < N; k++) {
            // 从 i+1 到 i 的权值为 0
            for (int i = 0; i + 1 < N; i++)
                if (d[i + 1] < INF)
                    d[i] = min(d[i], d[i + 1]);
            // 从AL到BL的权值为DL
            for (int i = 0; i < ML; i++)
                if (d[AL[i] - 1] < INF)
                    d[BL[i] - 1] = min(d[BL[i] - 1], d[AL[i] - 1] + DL[i]);
            // 从BD到AD的权值为 -DD
            for (int i = 0; i < MD; i++)
                if (d[BD[i] - 1] < INF)
                    d[AD[i] - 1] = min(d[AD[i] - 1], d[BD[i] - 1] - DD[i]); 
        }
        int res = d[N - 1];
        if (d[0] < 0) res = -1;    // 存在负圈,无解
        else if (res == INF) res = -2;
        printf("%d
    ", res);
    } 
    
    int SPFA(int s) {
        memset(d, INF, sizeof(d));
        memset(vis, 0, sizeof(vis));
        memset(inq, false, sizeof(inq));
        queue<P> q;
        q.push(P(d[s], s));
        d[s] = 0;
        inq[s] = true;
        
        while (!q.empty()) {
            P p = q.front();
            q.pop();
            int v = p.second;
            inq[v] = false;
            if (vis[v]++ > N) return -1;
            for (int  i = 1; i < G[v].size(); i++) {
                edge e = G[v][i];
                if (d[e.to] > d[v] + e.cost) {
                    d[e.to] = d[v] + e.cost;
                    if (!inq[e.to]) {
                        inq[e.to] = true;
                        q.push(P(d[e.to], e.to));
                    }
                }
            }
        }
        if (d[N] == INF) return -2;
        else return d[N];
    }
    int main() {
        cin >> N >> ML >> MD;
        for (int i = 0; i < ML; i++) {
            int AL, BL, DL;
            cin >> AL >> BL >> DL;
            G[AL].push_back((edge){BL, DL});
        }
        for (int i = 0; i < MD; i++) {
            int AD, BD, DD;
            cin >> AD >> BD >> DD;
            G[BD].push_back((edge){AD, -DD});
        }
        printf("%d
    ",SPFA(1));
        return 0;
    }
    View Code

    本篇学习自:夜深人静写算法(四) - 差分约束

  • 相关阅读:
    java图形化Swing教程(一)
    NYOJ467 中缀式变后缀式 【栈】
    火云开发课堂
    Egret项目Typescript的编译报错
    Java学习笔记(八):集合类
    Java学习笔记(七):内部类、静态类和泛型
    认识JavaScript的原型
    Java学习笔记(六):面向对象、接口和抽象类
    Java学习笔记(五):异常处理
    Java学习笔记(四):流程控制
  • 原文地址:https://www.cnblogs.com/astonc/p/10821751.html
Copyright © 2020-2023  润新知