差分约束

162 阅读6分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情


title: 差分约束 categories:

  • 常用算法
  • 图论 tags:
  • 差分约束

对于一系列形如xixj+ckx_i \leq x_j + c_k的不等式组,其中xix_i走到xjx_j都是自变量,ckc_k是一个常数。

考虑我们的最短路算法,假设从jj走到ii存在一条长度为cc的边,那么跑完最短路算法后,就必然有以下三角不等式成立:
dist[i]dist[j]+cdist[i] \leq dist[j] + c
原因很简单,因为当dist[i]>dist[j]+cdist[i] > dist[j] + c时,dist[i]dist[i]会被dist[j]+cdist[j] + c更新,因此对于每一个不等式xixj+ckx_i \leq x_j + c_k,我们可以连一条从jjii权值为ckc_k的的边,然后跑一遍最短路算法,获得一个可行解。
跑最短路算法就需要考虑图中存在负环的情况,因为负环图中不存在最短路,考虑图中边和不等式的对应关系如果有负环,就意味着有以下不等式成立: 假设三个点构成负环:

xixj+cj,xjxk+ck,xkxi+cix_i \leq x_j + c_j ,\quad x_j \leq x_k + c_k ,\quad x_k \leq x_i + c_i

则有: xixj+cj(xk+cj)+ck((xi+ci)+cj)+ckx_i \leq x_j + c_j \leq (x_k + c_j) + c_k \leq ((x_i + c_i) + c_j) + c_k 因为是负环,因此三条边之和小于零,推出矛盾: 0ci+cj+ck<00 \leq c_i + c_j + c_k < 0
由此可见,图中存在负环,等价于原不等式组无解,类似的思考流程可以推广到任意个点的负环。
另外我们也可以从最长路角度考虑,类似的,假设从ii走到jj存在一条长度为cc的边,那么跑完最长路算法后,就必然有以下三角不等式成立:
dist[j]dist[i]+cdist[j] \geq dist[i] + c 因此我们需要对原不等式xixj+ckx_i \leq x_j + c_k进行些许变化:xjxickx_j \geq x_i - c_k 我们可以连一条从iijj权值为ck-c_k的的边,然后跑一遍最短路算法,获得一个可行解。

以最短路方法为介绍一下具体步骤:

  1. 差分约束求不等式组的可行解
    源点需要满足的条件:从源点出发可以定可以走到所有的边,这是为了保证每一个不等式条件都能够被考虑到。一般来说,只要从源点出发能到所有点,则可以推出源点能到所有边,反之不一定成立,因为可能存在孤立点没有任何边与它相连。
    步骤:

    1. 先将每个不等式xixj+ckx_i \leq x_j + c_k,转化成一条从xjx_j走到xix_i,长度为ckc_k的一条边;
    2. 找一个超级源点,使得该源点一定可以遍历到所有边;
    3. 从源点求一遍单源最短路:
      结果1:如果存在负环,则原不等式组一定无解;
      结果2:如果没有负环,则dist[i]dist[i]就是原不等式组的一个可行解;
  2. 如何求最大值或最小值(指每个变量的最值)
    结论:如果求的是最小值,则应该求最长路,如果求的是最大值,则应该求最短路。
    对于求最值的问题一般来说,所有的点都有一个绝对值的限制,比如是所有点满足c\leq c 或者 c\geq c,否则只有相对关系的话,是无法求出最值的。
    那么引入问题1:如何转化xicix_i \geq c_i 或者 xicix_i \leq c_i,其中cic_i是一个常数(这类不等式只有一个变量)
    方法:建立一个超级源点,点值为0,然后建立从0到ii,长度为cic_i的边即可

    以求最大值为例,对于一个xix_i来说,一定存在一系列的链式关系,通过不断放缩得到一个最终和超级源点相关的不等式,如下:
    xixj+cjxk+ck+cj+...0+c1+c2+...x_i \leq x_j + c_j \leq x_k + c_k + c_j + ... \leq 0 + c_1 + c_2 + ... 为了求到xix_i的上界,我们需要找到所有从xix_i出发构成的的不等式链所计算出的xix_i的上界,然后在这些上界中取一个最小值,这样子就是我们的xix_i所能够取到的上界,即最大值。
    根据前文分析,每一个不等式链都可以对应到从0号点出发走到i号点的一个路径,而求上界的最小值,即等价于求图中的最短路。
    求最小值的思想也很对称,不再展开。

总结: 求不等式组的可行解,要找到一个超级源点,使得该源点一定可以遍历到所有边,然后从源点求单源最短路,如果存在负环,则原不等式组一定无解,如果没有负环,从源点到每个点的最短距离即为可行解;
如果是求最值问题,如果求最小值,对应图中求最长路,如果求最大值问题,对应在图中求最短路。

结合一个例题考虑:
Acwing 1169.糖果

幼儿园里有NN个小朋友,老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。

但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, 老师需要满足小朋友们的KK个要求。

幼儿园的糖果总是有限的,老师想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

输入格式 输入的第一行是两个整数NN, KK

接下来 KK 行,表示分配糖果时需要满足的关系,每行3个数字 XX,AA,BB

  1. 如果 X=1.表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
  2. 如果 X=2,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
  3. 如果 X=3,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
  4. 如果 X=4,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
  5. 如果 X=5,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。 小朋友编号从 1 到 N。

输出格式 输出一行,表示老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1。

数据范围 1≤N<105, 1≤K≤105, 1≤X≤5, 1≤A,B≤N

参考代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5, M = 3 * N;
int h[N], e[M], w[M], ne[M], idx;
//链式前向星存图
void add(int a, int b, int c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int dist[N], q[N], cnt[N], tt;
bool st[N];
int n, m;

//栈优化spfa求正环,同时求最长路
bool spfa() {
    q[++ tt] = 0;
    while(tt) {
        int u = q[tt --];
        st[u] = false;
        for(int i = h[u]; i != -1; i = ne[i]) {
            int j = e[i];
            if(dist[j] < dist[u] + w[i]) {
                dist[j] = dist[u] + w[i];
                cnt[j] = cnt[u] + 1;
                if(cnt[j] > n) return true;
                if(st[j]) continue;
                q[++ tt] = j;
                st[j] = true;
            }
        }
    }
    return false;
}

int main () {
    //加速输入
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n >> m;
    memset(h, -1, sizeof h);
    //将不等式关系转化为边
    while(m --) {
        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);
    }
    //从超级源点向所有点建边
    for(int i = 1; i <= n; i ++) add(0, i, 1);
    if(spfa()) puts("-1");
    //会爆int因此用ll
    else cout << reduce(dist + 1, dist + n + 1, 0ll) << endl;
    return 0;
}

ps:ps:这个题也可以用tarjantarjan算法求强连通分量,然后缩点,再求最长路搞定,强连通分量中存在边权为1的边,则意味着无解,此处不展开。