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)如何求最大值或者最小值

不等式组指的是
包含多个xi≤xj+ckx_i \leq x_j + c_kxixj+ck的不等式
举个例子:
{x1≤x2+1x2≤x3+2x3≤x1−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
通过差分约束 可以得到一组可行解
{x1=0x2=−1x3=−2 \left\{\begin{matrix} x_1 = 0 \\ x_2 = -1\\ x_3 = -2 \end{matrix}\right. x1=0x2=1x3=2
下面介绍差分约束 是如何求解的
可以将
xi≤xj+ck不等式看成是j⟶cki的边 x_i \leq x_j + c_k \\ 不等式看成是\\ j \stackrel{c_k}{\longrightarrow}i 的边 xixj+ckjcki

最短路转差分约束

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

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

差分约束转最短路

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

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

注意点

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

如果说某条边是孤立的边, 从源点到不了, 不会考虑它, 最终的结果可能不满足xi≤xj+ckx_i \leq x_j + c_kxixj+ck

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

这里的边统一指的是:xi≤xj+ckx_i \leq x_j + c_kxixj+ck .转化成的j⟶ckij \stackrel{c_k}{\longrightarrow} ijcki 的边

步骤:

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

xi≤xi+c1+c2+c3+....+...+ckx_i \leq x_i + c_1 + c_2 + c_3 + .... +... + c_kxixi+c1+c2+c3+....+...+ck , 如果不等式推出来的xi<xix_i < x_ixi<xi, 那么c1+c2+c3+....+...+ckc_1 + c_2 + c_3 + .... +... + c_kc1+c2+c3+....+...+ck 负数, 表示图中的环为负环

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

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

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

xi≤x0+cx_i \leq x_0 + cxix0+c, 超级源点x0x_0x0为0

比如说求xix_ixi的最值, xi≤xj+c1≤xk+c2+c1≤...≤x0(0)+c1+c2+...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+...
一定会推到x0x_0x0 超级源点为止, 否则依然是相对关系

以求xix_ixi最大值为例: 所有从xix_ixi出发, 构成的不等式链 xi≤xj+c1≤+xk+c2+c1≤...≤c1+c2+...+ckx_i \leq x_j + c_1 \leq + x_k + c_2 + c_1 \leq ... \leq c_1 + c_2 + ... + c_kxixj+c1+xk+c2+c1...c1+c2+...+ck所计算出的上界, 最终xix_ixi的最大值= 所有上界的最小值

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

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

在这里插入图片描述

AcWing 1169. 糖果

分析

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

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

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

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, 不会影响答案
此题关键 si: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. 排队布局

分析

xix_ixi:表示第i头奶牛的位置
奶牛之间有好感: xb−xa≤Lx_b - x_a \leq LxbxaL
奶牛之间反感: xb−xa≥Dx_b - x_a \geq DxbxaD
题目结果可能有3个分支

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

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

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

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

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

第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. 雇佣收银员

分析

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

  1. 需要满足选的人数不能超过来的人数 0≤xi≤num[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

因此xi−7+xi−6+...+xi≥rix_{i - 7} + x_{i - 6} + ... + x_{i} \geq r_ixi7+xi6+...+xiri

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

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

s0=0,si=x1+x2+...+xis_0 = 0, s_i = x_1 + x_2 + ... + x_is0=0,si=x1+x2+...+xi

第1个等式就变成了0≤si−si−1≤num[i]0 \leq s_i - s_{i - 1} \leq num[i]0sisi1num[i], 1≤i≤241 \leq i \leq 241i24

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

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

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

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

i≥8,si−si−8≥rii \geq 8, s_i - s_{i - 8} \geq r_ii8,sisi8ri

0<i<7,si+s24−si+16≥ri0 < i < 7, s_i + s_{24} - s_{i + 16} \geq r_i0<i<7,si+s24si+16ri

整理下

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

  • si−1≥si−num[i]s_{i - 1} \geq s_i - num[i]si1sinum[i]

  • i≥8,si≥si−8+rii \geq 8, s_i \geq s_{i - 8} + r_ii8,sisi8+ri

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

除了最后一组, 其他不等式都满足 xi≥xj+cx_i \geq x_j + cxixj+c的形式

最后一组3个s变量

如何处理这样的问题呢?

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

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

从0~1000里枚举每一个数s24s_{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号点开始求
在这里插入图片描述
为了表示s24s_24s24是一个固定的值s24=cs_{24} = cs24=c, 转化为2个不等式s24≥cs_{24} \geq cs24c, s24≤cs_{24} \leq cs24c, 并且多少ccc还不可以, 需要和某些变量联系在一起, x0=0x_0 = 0x0=0
s24≤s0+cs_{24} \leq s_0 + cs24s0+c, s0≤s24−cs_{0} \leq s_{24} - cs0s24c

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;
}
在 Vivado 中使用 Clocking Wizard 生成差分时钟需要经过一系列配置步骤。Clocking Wizard 是 Xilinx 提供的时钟管理 IP 核,支持生成包括差分时钟在内的多种时钟输出形式。以下是一个完整的配置流程: ### 3.1 Clocking Wizard 配置流程 在 Vivado 中打开 IP Catalog,搜索并选择 **Clocking Wizard**,然后进入“Customize IP”界面进行配置。 在“Output Clocks”选项卡中,可以为每个输出时钟配置频率、相位偏移、占空比等参数。要生成差分时钟,需确保输出时钟的“Type”设置为 **Differential**。这样生成的时钟将包含一对差分信号(例如 `clk_out1` 和 `clk_out1_b`)[^1]。 ### 3.2 差分时钟的实现方式 差分时钟通常通过 **LVDS**(Low Voltage Differential Signaling)标准实现,适用于高速时钟传输。在 Clocking Wizard 的输出配置中选择差分类型后,Vivado 将自动在输出端口生成差分对信号。 例如,若定义一个 100MHz 的差分输出时钟,系统将生成如下端口: ```verilog output clk_out1, output clk_out1_b ``` 这两个信号分别代表差分时钟的正端和负端,适用于驱动外部差分负载或高速接口。 ### 3.3 约束与布局布线 为了确保差分时钟在布局布线阶段被正确处理,需在 XDC 约束文件中添加差分引脚约束。例如: ```tcl set_property -dict { PACKAGE_PIN Y9 IOSTANDARD LVDS } [get_ports clk_out1] set_property -dict { PACKAGE_PIN Y8 IOSTANDARD LVDS } [get_ports clk_out1_b] ``` 此外,应使用 **`create_clock`** 约束定义差分时钟的主频,以确保时序分析工具能正确识别其频率特性[^3]。 ### 3.4 MMCM/PLL 配置与时钟源选择 Clocking Wizard 内部基于 MMCM(Mixed-Mode Clock Manager)或 PLL(Phase-Locked Loop)实现时钟管理功能。在配置过程中,可以选择输入时钟源,例如使用系统时钟(sys_clk)作为参考时钟源,该时钟通常设置为 200MHz。MMCM 会基于该输入生成所需的差分输出时钟,并可支持多个输出时钟之间的频率比和相位关系控制[^2]。 ### 3.5 差分时钟的应用场景 差分时钟广泛应用于高速数据传输、ADC/DAC 接口、DDR 控制器等场景中,其优势在于抗干扰能力强、信号完整性高。在 FPGA 设计中,差分时钟通常用于驱动高速串行接口(如 GigE Vision、Camera Link)或高速 ADC/DAC 芯片。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值