Loading

差分约束

差分约束问题是求形如\(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_k\leq0\),所以存在负环一定无解;反之,不等式组无解,对应图中存在负环。


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\n" , dist[50001]);
    
    return 0;
}

参考文献

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

posted @ 2020-11-26 12:16  Krocz  阅读(122)  评论(0编辑  收藏  举报