3.5 差分约束

接上节课
当我们发现spfa很长时间结束不了的话, 可以近似的认为存在负环
上一节课的方式:
通过定义int count = 0;
然后spfa循环次数超过一个定值, 比如10000
if (++ count > 10000) return true; 表示存在负环 / 正环(负环还是正环得根据看spfa求的是最短路还是最长路)

这里介绍一个新的方式, 结果上一定是正确的
经验上来说, 对于有负环的题目, 效果都会很好.
把SPFA中的队列换成
在SPFA中, 队列只是将等待更新的点, 用容器存起来, 之后在挨个弹出来去更新.其实从正确上来说, 用队列或者 没有任何区别, 唯一的是顺序问题
用栈的好处, 就是可以立马对更新的点, 查看是否能继续更新, 因此如果存在环的话, 能够更快的找到环

1165. 单词环

code(换成栈)

int t = q[-- tt] ;
因为当前队尾定义的是当前栈顶的位置, int hh = 0, tt = 0. 放入元素的时候是tt ++, tt会指向空的位置
所以弹的时候, 要直接先减, 再去q[-- tt];

#include <iostream>
#include <cstring>
using namespace std;
const int N = 700, M = 1e5 + 10;
int n;
int h[N], e[M], w[M], ne[M], idx;
int q[N], cnt[N];
double dist[N];
bool st[N];

void add(int a, int b, int c){
   e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

bool check(double mid){
   memset(st, 0, sizeof st);
   memset(cnt, 0 ,sizeof cnt);
   int hh = 0, tt = 0;
   for (int i = 0; i < 676; i ++ ){
       q[tt ++ ] = i;
       st[i] = true;
   }
   int count = 0;
   while (hh != tt){
       int t = q[-- tt];
       // if (hh == N) hh = 0;
       st[t] = false;
       
       for (int i = h[t]; ~i; i = ne[i]){
           int j = e[i];
           if (dist[j] < dist[t] +  w[i] - mid){
               dist[j] = dist[t] + w[i] - mid;
               cnt[j] = cnt[t] + 1;
               // if (++ count > 10000) return true; // 10000 是经验值
               if (cnt[j] >= N) return true;
               if (!st[j]){
                   st[j] = true;
                   q[tt ++ ] = j;
                   //if (tt == N) tt = 0;
               }
           }
       }
   }
   return false;
}
int main(){
   char str[1010];
   while (scanf("%d", &n), n){
       idx = 0;
       memset(h, -1, sizeof h);
       for (int i = 0; i < n; i ++ ){
           scanf("%s", str);
           int len = strlen(str);
           if (len >= 2){
               int left = (str[0] - 'a') * 26 + (str[1] - 'a');
               int right = (str[len - 2] - 'a') * 26 + (str[len - 1] - 'a');
               add(left, right, len);
           }
       }

       if (!check(0)) puts("No solution");
       else {
           double l = 0, r = 1010;
           while (r - l > 1e-4){ 
               double mid = (l + r) / 2;
               if (check(mid)) l = mid;
               else r = mid;
           }
           printf("%lf\n", r);
       }
   }
   return 0;
}

差分约束

(1)不等式组的可行解
(2)如何求最大值或者最小值

不等式组指的是
包含多个 x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck的不等式
举个例子:
{ x 1 ≤ x 2 + 1 x 2 ≤ x 3 + 2 x 3 ≤ x 1 − 2 \left\{\begin{matrix} x_1 \leq x_2 + 1 \\ x_2 \leq x_3 + 2 \\ x_3 \leq x_1 - 2 \end{matrix}\right. x1x2+1x2x3+2x3x12
通过差分约束 可以得到一组可行解
{ x 1 = 0 x 2 = − 1 x 3 = − 2 \left\{\begin{matrix} x_1 = 0 \\ x_2 = -1\\ x_3 = -2 \end{matrix}\right. x1=0x2=1x3=2
下面介绍差分约束 是如何求解的
可以将
x i ≤ x j + c k 不 等 式 看 成 是 j ⟶ c k i 的 边 x_i \leq x_j + c_k \\ 不等式看成是\\ j \stackrel{c_k}{\longrightarrow}i 的边 xixj+ckjcki

最短路转差分约束

比如在求最短路过程中, 在求完最短路后, 一定存在 d i s t [ i ] ≤ d i s t [ j ] + c k dist[i] \leq dist[j] + c_k dist[i]dist[j]+ck; 否则如果 d i s t [ i ] > d i s t [ j ] + c k dist[i] > dist[j] + c_k dist[i]>dist[j]+ck, 那么我们必然可以用 j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i jcki这条边去更新dist[i]
j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i jcki, 看成是一个不等式, x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck
只要我们的的图中不存在负环, 求完最短路后, 可以得到每一个dist值, 即 x i x_i xi的值, 那么dist[i]的值必然满足这个不等式

所以我们可以发现, 如果给我们一个图的话, 我们把每条边, 看成一个不等式, 那么在这个图中, 求每个点的到源点的最短距离, 求完之后的话, 每个点的值(每条边的不等式必然是满足的)
那么就是说 任何最短路的问题, 可以变成差分约束的问题(不等式组的问题)

差分约束转最短路

反过来来说, 对于任何不等式组中不等式, x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck 可以看成是从 j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i jcki 的边, 然后在图上随便 选择起点, 求一下每个点到起点的最短距离, 求完之后, 必然是满足 x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck.

因此, 每一个差分约束的问题转化成图论的单源最短路问题
因此, 但我们想求一个可行解的时候, 将不等式组中的每个不等式转化成一条边, 然后在图上求某一个点的单源最短路径, 求完之后, 必然满足所有限制条件, 那么得到了一个可行解. (一般情况下, 这是最简单的差分约束的做法)

注意点

起点需要满足几个条件, 不能随便任意取一个源点
源点需要满足的条件: 从源点出发, 一定可以走到所有的边, 因为只有从源点出发可以到达的边, 才可以满足 x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck. 换句话说, 所有从原点出发到达的边, 我们在算法中才会遍历到这条边, 才能够使得这条边满足 x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck .

如果说某条边是孤立的边, 从源点到不了, 不会考虑它, 最终的结果可能不满足 x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck

课堂疑问: 为什么是源点能到所有边, 而不是到所有点?
yxc: 因为点走不到没关系, 最多是孤立的点, 而题目要求的是边上的条件限制, 所以这里得加上源点 能走到所有边的条件

这里的边统一指的是: x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck .转化成的 j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i jcki 的边

步骤:

  1. 对于每个不等式, 我们需要想办法, 将 x i , x j , c k x_i, x_j, c_k xi,xj,ck, 变成 j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i jcki的边
  2. 找一个可以走到所有边的源点
  3. 从这个源点出发, 求一遍最短路
  4. 在这里插入图片描述
    注意: 这里并不是所有点都能求得最短路, 如果存在负环的话, 怎么办呢?
    还原到不等式中
    在这里插入图片描述
    推出了
    x 2 ≤ x 2 + c , c < 0 ⟹ x 2 < x 2 x_2 \leq x_2 + c, c < 0 \Longrightarrow x_2 < x_2 x2x2+c,c<0x2<x2
    总之, 如果图中存在负环, 那么不等式组存在矛盾, 因为在不等式中找到 x 2 < x 2 x_2 < x_2 x2<x2这种条件
    反过来也是一样, 如果利用不等式组的组合, 不断放缩, 也可以推出 x i < x i x_i < x_i xi<xi, 对应到图论问题中, 也表示存在负环.

x i ≤ x i + c 1 + c 2 + c 3 + . . . . + . . . + c k x_i \leq x_i + c_1 + c_2 + c_3 + .... +... + c_k xixi+c1+c2+c3+....+...+ck , 如果不等式推出来的 x i < x i x_i < x_i xi<xi, 那么 c 1 + c 2 + c 3 + . . . . + . . . + c k c_1 + c_2 + c_3 + .... +... + c_k c1+c2+c3+....+...+ck 负数, 表示图中的环为负环

因此,不等式无解<---->图中存在负环在这里插入图片描述
如果是通过最长路, 求可行解
原边: j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i jcki
不等式: x i ≤ x j + c k x_i \leq x_j + c_k xixj+ck 需要转化下 x j ≥ x i − c k x_j \geq x_i - c_k xjxick

求完最长路后, 必然满足 d i s t [ i ] ≥ d i s t [ j ] + c k dist[i] \geq dist[j] + c_k dist[i]dist[j]+ck ,
边也要变通下: i ⟶ − c k j i \stackrel{-c_k}{\longrightarrow} j ickj (边一定要根据不等式来建)
然后在新的边里求最长路即可
在这里插入图片描述
最长路里, 无解<->存在正环
更一般的形式
在这里插入图片描述
总结:
在这里插入图片描述
(2): 求得了可行解, 如何求最小的可行解/ 最大的可行解 , 这里的最值指的是每个变量的最值
结论1: 如果求的是最小值, 则应该求最长路; 如果求的是最大值, 则应该求最短路
如果是求最值的问题, 必然会有类似于 x i ≥ 0 x_i \geq 0 xi0 的条件; 否则, 如果所有不等式都是
x 1 ≤ x 2 + c 1 , x 2 ≤ x 3 + c 2 , . . . x k − 1 ≤ x k + c k − 1 x_1 \leq x_2 + c_1, \\ x_2 \leq x_3 + c_2, \\ ...\\ x_{k - 1} \leq x_k + c_{k - 1} x1x2+c1,x2x3+c2,...xk1xk+ck1
那么 x 1 , . . . x k x_1, ... x_k x1,...xk之间只有相对关系, 没有绝对关系.
假设只有相对关系, 没有绝对关系, 求得一个解 { x 1 , x 2 , . . . , x k } \{x_1, x_2, ..., x_k\} {x1,x2,...,xk}, 那么 { x 1 + d , x 2 + d , . . . , x k + d } \{x_1 + d, x_2 + d, ..., x_k + d\} {x1+d,x2+d,...,xk+d}也是一个可行解.
因此求最值的问题一定会有 x 0 ≤ 0 x_0 \leq 0 x00或者 x 0 ≥ 0 x_0 \geq 0 x00

问题: 如何转化 x i ≤ c x_i \leq c xic, 其中c是一个常数, 这类的不等式
方法: 建立一个超级源点, 0, 然后建立 0->i, 长度为c的边即可

x i ≤ x 0 + c x_i \leq x_0 + c xix0+c, 超级源点 x 0 x_0 x0为0

比如说求 x i x_i xi的最值, x i ≤ x j + c 1 ≤ x k + c 2 + c 1 ≤ . . . ≤ x 0 ( 0 ) + c 1 + c 2 + . . . x_i \leq x_j + c_1 \leq x_k + c_2 + c1 \leq ... \leq x_0(0) + c_1 + c_2 + ... xixj+c1xk+c2+c1...x0(0)+c1+c2+...
一定会推到 x 0 x_0 x0 超级源点为止, 否则依然是相对关系

以求 x i x_i xi最大值为例: 所有从 x i x_i xi出发, 构成的不等式链 x i ≤ x j + c 1 ≤ + x k + c 2 + c 1 ≤ . . . ≤ c 1 + c 2 + . . . + c k x_i \leq x_j + c_1 \leq + x_k + c_2 + c_1 \leq ... \leq c_1 + c_2 + ... + c_k xixj+c1+xk+c2+c1...c1+c2+...+ck所计算出的上界, 最终 x i x_i xi的最大值= 所有上界的最小值

在这里插入图片描述
比如上图, 求得 x i x_i xi的3个关系, 那么为了满足所有的这3个关系, 只能取上界的最小值 x i ≤ 2 x_i \leq 2 xi2, 因此 x i x_i xi的最大值 = 2

其实可以发现, 每一条不等式链, 就是一条从0号点出发, 走到i号点路径.
从后往前推的不等式, 可以发现上界就是图中一条从超级源点出发到i的最短路径的长度
在这里插入图片描述
如果求的是所有不等式的上界, 每一个上界可以转化为从0->i最短路径的长度,
求上界的最小值(即每个变量的最大值)等价于求从0到i的最短路径中的最小值, 求完后, 每个dist[i], 就是最小值, 求的是上界, 上界只能由最短路转化过来,
如果要求下界的最大值(即每个变量的最小值), 不等式链条 ≥ . . . . ≥ \geq .... \geq ...., 应该在所有下界里面求一个最大的, 应该用最长路去求, 因为求的是下界, 下界只能有最长路转化过来,

在这里插入图片描述

AcWing 1169. 糖果

分析

因为求的是最小值, 所以用最长路求解, 所有不等式转化为最长路的形式 >=
然后由条件 x i ≥ 1 x_i \geq 1 xi1, 建立超级源点 x 0 = 0 x_0 = 0 x0=0, 并且因为 x i ≥ x 0 + 1 x_i \geq x_0 + 1 xix0+1, 表明超级源点 0 0 0, 可以到任何点 i i i, 所以可以到任何边

(注意:可以到任何点 ->可以到任何边, 但是可以到任何边 ❌->任何点, 因为点可能是孤立点

从0号点求一遍单源最长路, 就可以了, 就可以 x i x_i xi最小值了
在这里插入图片描述

code

最坏的情况都是第1种情况, 建2条边, 然后超级源点1条边, 最坏得建3倍的边
求所有距离和, 最坏情况下1 < 2 < 3 < 4 < … < , 有N个不等式关系, 然后将所有距离加起来N^2, 会爆int
所以用LL dist

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;

const int N = 100010, M = 300010;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
LL dist[N];
int q[N], cnt[N];
bool st[N];

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

bool spfa(){
    int hh = 0, tt = 1;
    memset(dist, -0x3f, sizeof dist);
    dist[0] = 0;
    q[0] = 0;
    st[0] = true;
    
    while (hh != tt){
        int t = q[-- tt];
        if (hh == N) hh = 0;
        st[t] = false;
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] < dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n + 1) return false;
                if (!st[j]){
                    st[j] = true;
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
    return true;
    
}
int main(){
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    while (m -- ){
        int x, a, b;
        scanf("%d%d%d", &x, &a, &b);
        if (x == 1) add(b, a, 0), add(a, b, 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");
    else {
        LL res = 0;
        for (int i = 1; i <= n; i ++ ) res += dist[i];
        printf("%lld\n", res);
    }
    
    return 0;
}

AcWing 362. 区间

分析

由于需要用到前缀和, 但点是[0, 500000], 所以需要对区间平移, 左右区间都+1, 不会影响答案
此题关键 s i : s_i: si: 1~i中被选出的数的个数, 拿来建图
还有题目一定有解, 一定不存在正环
在这里插入图片描述

code

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 3e5 + 10;
int h[N], e[M], w[M], ne[M], idx;
int n;
bool st[N];
int dist[N];
int q[N];

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void spfa(){
    memset(dist, -0x3f, sizeof dist);
    st[0] = true;
    q[0] = 0;
    dist[0] = 0;
    int hh = 0, tt = 1;
    while (hh != tt){
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] < dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                if (!st[j]){
                    st[j] = true;
                    q[tt ++] = j;
                    if (tt == N) tt = 0;
                }
            }
            
        }
    }
}

int main(){
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    
    for (int i = 1; i < N; i ++ ){
        add(i - 1, i, 0); // si >= s{i - 1}
        add(i, i - 1, -1); // s_{i - 1} >= s_i - 1
    }
    
    for (int i = 0; i < n; i ++ ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        a ++, b ++;
        add(a - 1, b, c); // s_b >= s_a + c
    }

    spfa();
    
    printf("%d\n", dist[50001]);
    
    return 0;
}

AcWing 1170. 排队布局

分析

x i x_i xi:表示第i头奶牛的位置
奶牛之间有好感: x b − x a ≤ L x_b - x_a \leq L xbxaL
奶牛之间反感: x b − x a ≥ D x_b - x_a \geq D xbxaD
题目结果可能有3个分支

  1. 没有合法的方案 --> -1 --> 判断有没有正负环
  2. 1号奶牛和N好奶牛距离可以任意大(??)
  3. 1号奶牛和N号奶牛的最大距离 -> 最短路

题目给了条件, 所有奶牛按顺序排列, 在同一个点上可以放多个奶牛
所有 x i ≤ x i + 1 , 1 ≤ i < n x_i \leq x_{i +1}, 1 \leq i < n xixi+1,1i<n (i不能=n, =n的话, i + 1就超过n了) --> i + 1 → i i + 1 \to i i+1i, 边权0
因为要求最短路, 需要转化成 x i ≤ x j + c x_i \leq x_j + c xixj+c的形式
奶牛好感的条件转化为 x b ≤ x a + L x_b \leq x_a + L xbxa+L --> a → b a \to b ab, 边权L
反感的条件 x a ≤ x b − D x_a \leq x_b - D xaxbD --> b → a b \to a ba, 边权(-D)
需要验证下, 有没有一个超级源点能够到达所有点
条件中好像没有一个点, 能够无条件到达所有点, 因此需要建立超级源点
上述的所有关系, 都是相对关系, 因此可以在数轴上任意移动, 可以假定他们在数轴<=0的半边.

因为我们要从0号点向其他所有点连边, 所以0应该在上述不等式的右边, 即 x i ≤ x 0 x_i \leq x_0 xix0, 要想满足这样的关系, 得假设所有点在0的左边.
有以上等式就可以连边 0 → i 0 \to i 0i, 边权0

第1问, 判断有无解, 可以建立超级源点, 判断有没有负环
第2问, 距离无限大? 由于问的是绝对值, 然后条件是相对值, 可以固定1号点的位置, 不会影响其他点的相对关系, 把 x 1 x_1 x1固定成0, 然后在这样的条件下, 判断下 x n x_n xn是否可以无限大->求 x n x_n xn的最大值, 其实就是求下1号到其他所有点的最短路径, 求完之后, x n x_n xn == INF, 那么就是无限大

在图论中, 1到n的最短路是 + ∞ +\infty +的话, 对应到不等式组里, 如果我们想找这样的链式关系 x n ≤ x n − 1 + . . . ≤ . . . ≤ x 1 + . . . x_n \leq x_{n - 1} + ... \leq ...\leq x_1 + ... xnxn1+......x1+..., 但是 x n = = + ∞ x_n == +\infty xn==+, 表示的是1号点到n号点不存在路径, 即不存在 x n ≤ x n − 1 + . . . ≤ . . . ≤ x 1 + . . . x_n \leq x_{n - 1} + ... \leq ...\leq x_1 + ... xnxn1+......x1+...链式关系, 即 x n x_n xn x 1 x_1 x1之间没有限制关系

第3问, 如果第2问求得的距离不是 + ∞ +\infty +, 那么就是第3问的解
在这里插入图片描述

code

第1问判断负环, 应该将所有点加入队列; 第2问应该只将1号点加入队列, 因此要调用spfa两次
第1种边n条, 后2种边10000条, 因此总共21000条, 为防止边界问题 + 10条

#include <iostream>
#include <cstring>
using namespace std;
const int N = 1010, M = 20010, INF = 0x3f3f3f3f;
int dist[N];
bool st[N];
int q[N];
int n, m1, m2;
int h[N], e[M], w[M], ne[M], idx;
int cnt[N];


void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

bool spfa(int size){
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    memset(cnt, 0, sizeof cnt);
    
    int hh = 0, tt = 0;
    for (int i = 1; i <= size; i ++ ){
        q[tt ++ ] = i;
        dist[i] = 0;
        st[i] = true;
    }
    
    while (hh != tt){
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;
        
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] > dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= n) return true;
                if (!st[j]){
                    st[j] = true;
                    q[tt ++] = j;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
    return false;
}

int main(){
    cin >> n >> m1 >> m2;
    memset(h, -1, sizeof h);
    
    for (int i = 1; i < n; i ++ ) add(i + 1, i, 0);
    while (m1 -- ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        if (a > b) swap(a, b);
        add(a, b, c);
    }
    while (m2 -- ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        if (a > b) swap(a, b);
        add(b, a, -c);
    }
    
    if (spfa(n)) puts("-1"); // spfa传入size参数, 表示初始状态要加多少到点到队列中, 判断负环, 要将所有点加入
    else {
        spfa(1); // 求最短距离, 只用加入起点
        if (dist[n] == INF) puts("-2");
        else cout << dist[n] << endl;
    }
    
    return 0;
}

393. 雇佣收银员

分析

问的是最小值, 不出意外的话, 用的是最长路来求解
首先来看下, 怎么构造不等式关系
n u m s [ i ] nums[i] nums[i]:表示每一个时刻来的人数, n u m [ 0 ] num[0] num[0]表示0点来的人数, n u m s [ 23 ] nums[23] nums[23]表示23点来的人数
x i x_i xi:表示最终从 n u m s [ i ] nums[i] nums[i]里挑的人数

  1. 需要满足选的人数不能超过来的人数 0 ≤ x i ≤ n u m [ i ] 0 \leq x_i \leq num[i] 0xinum[i]
  2. 要满足要求, 每个小时需要满足最低收银员人数要求

5点来工作的收银员可以工作 5, 6, …, 12
想看下服务i的人是不是足够, 那么谁能够服务i呢,
i-7能够服务i,举个例子, 5能够服务12, 因此12 - 7 = 5, 能够服务i, 以此类推 i - 6, … i

因此 x i − 7 + x i − 6 + . . . + x i ≥ r i x_{i - 7} + x_{i - 6} + ... + x_{i} \geq r_i xi7+xi6+...+xiri

然后它不符合差分约束的一般的形式, 然后我们发现它每次只加一段和, 其实可以用前缀和的思想

如果用前缀和的话, 就需要将所有下标往后移动1位

s 0 = 0 , s i = x 1 + x 2 + . . . + x i s_0 = 0, s_i = x_1 + x_2 + ... + x_i s0=0,si=x1+x2+...+xi

第1个等式就变成了 0 ≤ s i − s i − 1 ≤ n u m [ i ] 0 \leq s_i - s_{i - 1} \leq num[i] 0sisi1num[i], 1 ≤ i ≤ 24 1 \leq i \leq 24 1i24

第2个等式算前缀和的时候需要分段考虑(因为23点后接着0点), 从>=8 开始前缀和算的时候只有一段,

当从前面开始算前缀和的时候, 有一段是前面的, 有一段是后面的, 两段和

找下规律, i = 7 i = 7 i=7, 需要加上 s 24 − s 23 s_{24} - s_{23} s24s23

i = 6 i = 6 i=6, 需要加上 s 24 − s 22 s_{24} - s_{22} s24s22

i ≥ 8 , s i − s i − 8 ≥ r i i \geq 8, s_i - s_{i - 8} \geq r_i i8,sisi8ri

0 < i < 7 , s i + s 24 − s i + 16 ≥ r i 0 < i < 7, s_i + s_{24} - s_{i + 16} \geq r_i 0<i<7,si+s24si+16ri

整理下

  • s i ≥ s i − 1 + 0 s_i \geq s_{i - 1} + 0 sisi1+0

  • s i − 1 ≥ s i − n u m [ i ] s_{i - 1} \geq s_i - num[i] si1sinum[i]

  • i ≥ 8 , s i ≥ s i − 8 + r i i \geq 8, s_i \geq s_{i - 8} + r_i i8,sisi8+ri

  • 0 < i ≤ 7 , s i ≥ s i + 16 − s 24 + r i 0 < i \leq 7, s_i \geq s_{i + 16} - s_{24} + r_i 0<i7,sisi+16s24+ri

除了最后一组, 其他不等式都满足 x i ≥ x j + c x_i \geq x_j + c xixj+c的形式

最后一组3个s变量

如何处理这样的问题呢?

不把 s 24 s_{24} s24 当作变量, 直接枚举下 s 24 s_{24} s24的所有取值, 对于所有的枚举 s 24 s_{24} s24, s 24 s_{24} s24就是一个常量, 然后最后一个等式就变成了标准的形式

最终有解的话, 输出所有成员的和, 即前缀和 s 24 s_{24} s24

从0~1000里枚举每一个数 s 24 s_{24} s24, 找到第一个枚举的值, 使得这个问题是有解的, 那么这个值就是我们要找的最小值,

当我们枚举完0~1000都是无解的, 说明这个问题是无解的

数据范围[0, 1000], 所以只会枚举1001次, 边数的话3大组, 24 * 3 ~= 70多条边

1000 * 100 * 20 = 2 * 10^7 (1000次循环 * 100条边* 20组数组)

检验下超级源点能否到所有点, 只用第1组条件, 0->1 -> 2 -> … ->24

因此0号点能到所有点, 从0号点开始求
在这里插入图片描述
为了表示 s 2 4 s_24 s24是一个固定的值 s 24 = c s_{24} = c s24=c, 转化为2个不等式 s 24 ≥ c s_{24} \geq c s24c, s 24 ≤ c s_{24} \leq c s24c, 并且多少 c c c还不可以, 需要和某些变量联系在一起, x 0 = 0 x_0 = 0 x0=0
s 24 ≤ s 0 + c s_{24} \leq s_0 + c s24s0+c, s 0 ≤ s 24 − c s_{0} \leq s_{24} - c s0s24c

code

#include <iostream>
#include <cstring>
using namespace std;

const int N = 30, M = 100, INF = 0x3f3f3f3f;

int n, h[N], e[M], w[M], ne[M], idx;
int r[N], num[N];
int dist[N];
int q[N], cnt[N];
bool st[N];

void add(int a, int b, int c){
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void build(int c){
    memset(h, -1, sizeof h);
    idx = 0;
    add(0, 24, c), add(24, 0, -c); // s24常量的边
    for (int i = 1; i <= 7; i ++ ) add(i + 16, i, r[i] - c); // 前缀和的边
    for (int i = 8; i <= 24; i ++ ) add(i - 8, i, r[i]); // 前缀和的边
    for (int i = 1; i <= 24; i ++ ){
        add(i, i - 1, -num[i]); // 第2组边
        add(i - 1, i, 0); // 第1组边
    }
}

bool spfa(int c){
    build(c);
    
    memset(dist, -0x3f, sizeof dist);
    memset(cnt, 0, sizeof cnt);
    memset(st, 0, sizeof st);
    
    int hh = 0, tt = 1;
    dist[0] = 0;
    q[0] = 0;
    st[0] = true;
    
    while (hh != tt){
        int t = q[hh ++ ];
        if (hh == N) hh = 0;
        st[t] = false;
        
        for (int i = h[t]; ~i; i = ne[i]){
            int j = e[i];
            if (dist[j] < dist[t] + w[i]){
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if (cnt[j] >= 25) return false; // 0-23总共24个点 + 超级源点
                if (!st[j]){
                    st[j] = true;
                    q[tt ++ ] = j;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
    return true;
}

int main(){
    int T;
    cin >> T;
    while (T --){
        for (int i = 1; i <= 24; i ++ ) cin >> r[i];
        cin >> n;
        memset(num, 0, sizeof num);
        for (int i = 0; i < n; i ++ ){
            int t;
            cin >> t;
            num[t + 1] ++;
        }

        bool success = false;
        for (int i = 0; i <= 1000; i ++ )
            if (spfa(i)){
                cout << i << endl;
                success = true;
                break;
            }
        if (!success) puts("No Solution");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值