zoukankan      html  css  js  c++  java
  • 差分约束

    差分约束问题是求形如(x_i leq x_j + c_k)的一组不等式的解,且求解的方式是转化为图论中的求单源最短路。

    转化为求单源最短路

    • 不等式 (x_i leq x_j + c_k) 和图中一条从(x_i)(x_j)长度为(c_k)的边相对应;
    • 若在图中求单源最短路,则有dist[i] <= dist[j] + k
    • 因此,可以将求不等式组的可行解转换成在对应图中求单源最短路

    Part1.求不等式组的可行解

    • 源点需要满足的条件:从源点出发,一定可以走到所有的边(否则所求结果并未满足全部的约束条件)

    步骤

    1. 先将每一个不等式(x_i leq x_j + c_k),转化成一条从(x_j)走到(x_i),长度为(c_k)的一条边
    2. 找一个超级源点,使得该源点一定可以遍历所有边
    3. 从源点求一遍单源最短路

    负环

    如果存在负环,则原不等式组一定无解,反之dist数组会存放原不等式组的一组解

    证明:

     x1 —— x2 —— x3
     |            |
     xk  ——...—— x4
    

    假设(x_1,x_2,x_3,...,x_k)构成负环,则可推知 (x_1 leq x_k+c_k leq x_{k-1}+c_{k-1}+c_k leq...leq x_1+c_1+...+c_k);因为是负环,所以(c_1+...+c_kleq0),所以存在负环一定无解;反之,不等式组无解,对应图中存在负环。


    Part2.如何求最大值或者最小值(每一个变量(x_i)的最值)

    此类差分约束问题的特点

    (x_i leq x_j + c_k) 表示(x_i)(x_j)存在相对关系,(x_i leq c)表示(x_i)的绝对关系;如果不等式组表述的都是相对关系,则不等式组要么无解,如果有解则必有无穷多解;所以在要求最大值/最小值时,题目必然会给出一个绝对条件

    绝对条件(x_i leq c)的处理方式

    建立一个超级源点(0) ,建立一个(0)指向(x_i)且长度为(c)的边

    结论

    如果求最小值,则应该求最长路;如果求的是最大值,则应该求最短路

    证明:

    以求(x_i)的最大值为例:

     x0 —— x1 —— x2 ——...—— xi
    
    1. 首先,从源点(0)(x_i)的一条路径(如(x_0,x_1,x_3,...,x_{i-1},x_i))可表述为不等式链(x_i leq x_{i-1}+c_{i-1} leq x_{i-2}+c_{i-1}+c_{i-2} leq x_1+c_1+...+c_{i-1}leq c_0+c_1+...+c_{i-1})
    2. (x_i leq c_0+c_1+...+c_{i-1}),也就是这条路径的长度
    3. 且需要满足所有的不等式链(路径),则应该求出所有路径中的最小值((x_i)应该小于等于所有路径长度中的最小值),也即应该求最短路。最终求出的dist[i]就是(x_i)可以取到的最大值

    例题-糖果

    题目链接

    题解

    • 题目要求最小值,则应该求最长路
    • 根据题意,可得出不等式组:
    //求最长路,则关系式应使用 >= 描述;例如A >= B+1,表示 dist[A] >= dist[B] + 1
    x = 1   A >= B 且 B >= A
    x = 2   B >= A + 1
    x = 3   A >= B
    x = 4   A >= B + 1
    x = 5   B >= A
    以及隐含条件“每个小朋友都能够分到糖果”:
    假设一个超级源点X0,则任意节点满足: X >= X0 + 1
    
    • 检测:从源点0出发可以走到所有的点,则必然能够走到所有边;源点0满足条件
    • 使用spfa算法求最长路,同时检测是否存在正环。如果存在正环,表示无解;反之所有dist[1~n]的和即为糖果的最小值

    代码

    #include <iostream>
    #include <cstring>
    using namespace std;
    typedef long long LL;
    const int N = 100010 , M = 300010;
    int e[M] , ne[M] , w[M] , h[N] , idx;
    LL dist[N];
    int stack[N] , cnt[N];
    bool st[N];
    int n,m;
    
    void add(int l, int r, int d)
    {
        e[idx] = r , w[idx] = d,  ne[idx] = h[l],  h[l] = idx++;
    }
    
    bool spfa()
    {
        memset(dist , -0x3f , sizeof dist);
        int hh = 0, tt = 0;
        stack[0] = 0;
        dist[0] = 0;
        
        while(hh <= tt)
        {
            int u = stack[tt --];
            st[u] = false;
            
            for(int i = h[u]; ~i; i = ne[i])
            {
                int v = e[i];
                if(dist[v] < dist[u] + w[i])
                {
                    dist[v] = dist[u] + w[i];
                    cnt[v] = cnt[u] + 1;
                    if(cnt[v] >= n + 1) return false;
                    if(!st[v]) st[v] = true , stack[++ tt] = v;
                }
            }
        }
        return true;
    }
    
    int main()
    {
        cin >> n >> m;
        memset(h , -1 , sizeof h);
        
        for(int i = 1; i <= n; i++)  add(0 , i , 1);
        for(int i  = 0; i < m; i++)
        {
            int x , a , b;
            cin >> x >> a >> b;
            if(x == 1) add(a , b , 0) , add(b , a , 0);
            else if(x == 2) add(a , b , 1);
            else if(x == 3) add(b , a , 0);
            else if(x == 4) add(b , a,  1);
            else add(a , b , 0);
        }
        
        if(!spfa()) puts("-1");
        else 
        {
            LL ans = 0;
            for(int i = 1; i <= n; i++) 
                ans += dist[i];
            cout << ans << endl;
        }
        return 0;
    }
    

    注意点

    一般来说,spfa算法使用队列,可以减少更新次数,从而减少运行时间;但是仅就检测负环问题,使用栈来代替队列会运行得更快,因为栈先进先出得性质能更快的在负环中不断迭代。本题中就使用了栈来代替队列,否则会TLE。

    例题-区间

    题目链接

    题解

    • 题目要求最小值,则应该求最长路
    • 对于任意一个区间[a,b]Xa + Xa+1 + ... + Xb >= c (Xi=1表示选择了i,Xi=0表示没有选择)
    • 显然利用前缀和可以将上式转化为符合差分约束关系的一般形式:Sb - Sa-1 >= c
    • 还应满足的约束条件是:0 <= Xi <= 1 即 0 <= (Si - Si-1) <= 1
    总结:
    Sb >= Sa-1 + c
    Si >= Si-1 + 0
    Si-1 >= Si - 1
    
    • 因为使用前缀和,所以需要将区间下标映射到[1 , 50001],将0空出来;同时由Si >= Si-1 + 0知,0可以作为源点,使得可以遍历所有的边

    代码

    #include <iostream>
    #include <cstring>
    using namespace std;
    const int N = 50010 , M = N * 3;
    int e[M] , ne[M] , w[M] , h[N] , idx;
    int dist[N] , q[N];
    bool st[N];
    int n;
    
    void add(int l ,int r, int v)
    {
        e[idx] = r , w[idx] = v , ne[idx] = h[l] , h[l] = idx ++;
    }
    
    void spfa()
    {
        memset(dist , -0x3f , sizeof dist);
        dist[0] = 0;
        int hh = 0 , tt = 1;
        q[0] = 0;
        
        while(hh != tt)
        {
            int u = q[hh ++];
            if(hh == N) hh = 0;
            
            st[u] = false;
            
            for(int i = h[u]; ~i; i = ne[i])
            {
                int v = e[i];
                if(dist[v] < dist[u] + w[i])
                {
                    dist[v] = dist[u] + w[i];
                    if(!st[v])
                    {
                        st[v] = true;
                        q[tt++] = v;
                        if(tt == N) tt = 0;
                    }
                }
            }
        }
    }
    
    int main()
    {
        cin >> n;
        memset(h , -1, sizeof h);
        for(int i = 1; i <= 50001; i++) add(i-1 , i , 0) , add(i , i-1 , -1);
        
        for(int i = 0; i < n; i++)
        {
            int a, b , c;
            cin >> a >> b >> c;
            add(a , b + 1, c);    
        }
        
        spfa();
        
        printf("%d
    " , dist[50001]);
        
        return 0;
    }
    

    参考文献

    Acwing-算法提高课-图论章节
    https://www.acwing.com/activity/content/introduction/16/

  • 相关阅读:
    MyBatis 笔记
    Python os模块、os.path模块常用方法
    vue-lazyload 的使用(vue图片懒加载)
    使用 vant 的 v-lazy 实现图片 vue 在移动端的懒加载
    代码注释规范-IDEA 配置 Java 类方法注释模板
    Java Web 笔记(杂)
    tortoisegit使用
    git结合github远程仓库使用
    .doc 2 .docx可用代码
    惊奇,MySQL还能正则匹配,简易例子
  • 原文地址:https://www.cnblogs.com/zy200128/p/14041612.html
Copyright © 2011-2022 走看看