接上节课
当我们发现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
xi≤xj+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.
⎩⎨⎧x1≤x2+1x2≤x3+2x3≤x1−2
通过差分约束 可以得到一组可行解
{
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 的边
xi≤xj+ck不等式看成是j⟶cki的边
最短路转差分约束
比如在求最短路过程中, 在求完最短路后, 一定存在
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
j⟶cki这条边去更新dist[i]
将
j
⟶
c
k
i
j \stackrel{c_k}{\longrightarrow} i
j⟶cki, 看成是一个不等式,
x
i
≤
x
j
+
c
k
x_i \leq x_j + c_k
xi≤xj+ck
只要我们的的图中不存在负环, 求完最短路后, 可以得到每一个dist值, 即
x
i
x_i
xi的值, 那么dist[i]的值必然满足这个不等式
所以我们可以发现, 如果给我们一个图的话, 我们把每条边, 看成一个不等式, 那么在这个图中, 求每个点的到源点的最短距离, 求完之后的话, 每个点的值(每条边的不等式必然是满足的)
那么就是说 任何最短路的问题, 可以变成差分约束的问题(不等式组的问题)
差分约束转最短路
反过来来说, 对于任何不等式组中不等式, x i ≤ x j + c k x_i \leq x_j + c_k xi≤xj+ck 可以看成是从 j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i j⟶cki 的边, 然后在图上随便 选择起点, 求一下每个点到起点的最短距离, 求完之后, 必然是满足 x i ≤ x j + c k x_i \leq x_j + c_k xi≤xj+ck.
因此, 每一个差分约束的问题转化成图论的单源最短路问题
因此, 但我们想求一个可行解的时候, 将不等式组中的每个不等式转化成一条边, 然后在图上求某一个点的单源最短路径, 求完之后, 必然满足所有限制条件, 那么得到了一个可行解. (一般情况下, 这是最简单的差分约束的做法)
注意点
起点需要满足几个条件, 不能随便任意取一个源点
源点需要满足的条件: 从源点出发, 一定可以走到所有的边, 因为只有从源点出发可以到达的边, 才可以满足
x
i
≤
x
j
+
c
k
x_i \leq x_j + c_k
xi≤xj+ck. 换句话说, 所有从原点出发到达的边, 我们在算法中才会遍历到这条边, 才能够使得这条边满足
x
i
≤
x
j
+
c
k
x_i \leq x_j + c_k
xi≤xj+ck .
如果说某条边是孤立的边, 从源点到不了, 不会考虑它, 最终的结果可能不满足 x i ≤ x j + c k x_i \leq x_j + c_k xi≤xj+ck
课堂疑问: 为什么是源点能到所有边, 而不是到所有点?
yxc: 因为点走不到没关系, 最多是孤立的点, 而题目要求的是边上的条件限制, 所以这里得加上源点 能走到所有边的条件
这里的边统一指的是: x i ≤ x j + c k x_i \leq x_j + c_k xi≤xj+ck .转化成的 j ⟶ c k i j \stackrel{c_k}{\longrightarrow} i j⟶cki 的边
步骤:
- 对于每个不等式, 我们需要想办法, 将 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 j⟶cki的边
- 找一个可以走到所有边的源点
- 从这个源点出发, 求一遍最短路
注意: 这里并不是所有点都能求得最短路, 如果存在负环的话, 怎么办呢?
还原到不等式中
推出了
x 2 ≤ x 2 + c , c < 0 ⟹ x 2 < x 2 x_2 \leq x_2 + c, c < 0 \Longrightarrow x_2 < x_2 x2≤x2+c,c<0⟹x2<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 xi≤xi+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
j⟶cki
不等式:
x
i
≤
x
j
+
c
k
x_i \leq x_j + c_k
xi≤xj+ck 需要转化下
x
j
≥
x
i
−
c
k
x_j \geq x_i - c_k
xj≥xi−ck
求完最长路后, 必然满足
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
i⟶−ckj (边一定要根据不等式来建)
然后在新的边里求最长路即可
最长路里, 无解<->存在正环
更一般的形式
总结:
(2): 求得了可行解, 如何求最小的可行解/ 最大的可行解 , 这里的最值指的是每个变量的最值
结论1: 如果求的是最小值, 则应该求最长路; 如果求的是最大值, 则应该求最短路
如果是求最值的问题, 必然会有类似于
x
i
≥
0
x_i \geq 0
xi≥0 的条件; 否则, 如果所有不等式都是
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}
x1≤x2+c1,x2≤x3+c2,...xk−1≤xk+ck−1
那么
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
x0≤0或者
x
0
≥
0
x_0 \geq 0
x0≥0
问题: 如何转化
x
i
≤
c
x_i \leq c
xi≤c, 其中c是一个常数, 这类的不等式
方法: 建立一个超级源点, 0, 然后建立 0->i, 长度为c的边即可
x i ≤ x 0 + c x_i \leq x_0 + c xi≤x0+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 + ...
xi≤xj+c1≤xk+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 xi≤xj+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
xi≤2, 因此
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
xi≥1, 建立超级源点
x
0
=
0
x_0 = 0
x0=0, 并且因为
x
i
≥
x
0
+
1
x_i \geq x_0 + 1
xi≥x0+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
xb−xa≤L
奶牛之间反感:
x
b
−
x
a
≥
D
x_b - x_a \geq D
xb−xa≥D
题目结果可能有3个分支
- 没有合法的方案 --> -1 --> 判断有没有正负环
- 1号奶牛和N好奶牛距离可以任意大(??)
- 1号奶牛和N号奶牛的最大距离 -> 最短路
题目给了条件, 所有奶牛按顺序排列, 在同一个点上可以放多个奶牛
所有
x
i
≤
x
i
+
1
,
1
≤
i
<
n
x_i \leq x_{i +1}, 1 \leq i < n
xi≤xi+1,1≤i<n (i不能=n, =n的话, i + 1就超过n了) -->
i
+
1
→
i
i + 1 \to i
i+1→i, 边权0
因为要求最短路, 需要转化成
x
i
≤
x
j
+
c
x_i \leq x_j + c
xi≤xj+c的形式
奶牛好感的条件转化为
x
b
≤
x
a
+
L
x_b \leq x_a + L
xb≤xa+L -->
a
→
b
a \to b
a→b, 边权L
反感的条件
x
a
≤
x
b
−
D
x_a \leq x_b - D
xa≤xb−D -->
b
→
a
b \to a
b→a, 边权(-D)
需要验证下, 有没有一个超级源点能够到达所有点
条件中好像没有一个点, 能够无条件到达所有点, 因此需要建立超级源点
上述的所有关系, 都是相对关系, 因此可以在数轴上任意移动, 可以假定他们在数轴<=0的半边.
因为我们要从0号点向其他所有点连边, 所以0应该在上述不等式的右边, 即 x i ≤ x 0 x_i \leq x_0 xi≤x0, 要想满足这样的关系, 得假设所有点在0的左边.
有以上等式就可以连边 0 → i 0 \to i 0→i, 边权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 + ... xn≤xn−1+...≤...≤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 + ... xn≤xn−1+...≤...≤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]里挑的人数
- 需要满足选的人数不能超过来的人数 0 ≤ x i ≤ n u m [ i ] 0 \leq x_i \leq num[i] 0≤xi≤num[i]
- 要满足要求, 每个小时需要满足最低收银员人数要求
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 xi−7+xi−6+...+xi≥ri
然后它不符合差分约束的一般的形式, 然后我们发现它每次只加一段和, 其实可以用前缀和的思想
如果用前缀和的话, 就需要将所有下标往后移动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] 0≤si−si−1≤num[i], 1 ≤ i ≤ 24 1 \leq i \leq 24 1≤i≤24
第2个等式算前缀和的时候需要分段考虑(因为23点后接着0点), 从>=8 开始前缀和算的时候只有一段,
当从前面开始算前缀和的时候, 有一段是前面的, 有一段是后面的, 两段和
找下规律, i = 7 i = 7 i=7, 需要加上 s 24 − s 23 s_{24} - s_{23} s24−s23
i = 6 i = 6 i=6, 需要加上 s 24 − s 22 s_{24} - s_{22} s24−s22
i ≥ 8 , s i − s i − 8 ≥ r i i \geq 8, s_i - s_{i - 8} \geq r_i i≥8,si−si−8≥ri
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+s24−si+16≥ri
整理下
-
s i ≥ s i − 1 + 0 s_i \geq s_{i - 1} + 0 si≥si−1+0
-
s i − 1 ≥ s i − n u m [ i ] s_{i - 1} \geq s_i - num[i] si−1≥si−num[i]
-
i ≥ 8 , s i ≥ s i − 8 + r i i \geq 8, s_i \geq s_{i - 8} + r_i i≥8,si≥si−8+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<i≤7,si≥si+16−s24+ri
除了最后一组, 其他不等式都满足 x i ≥ x j + c x_i \geq x_j + c xi≥xj+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
s24≥c,
s
24
≤
c
s_{24} \leq c
s24≤c, 并且多少
c
c
c还不可以, 需要和某些变量联系在一起,
x
0
=
0
x_0 = 0
x0=0
s
24
≤
s
0
+
c
s_{24} \leq s_0 + c
s24≤s0+c,
s
0
≤
s
24
−
c
s_{0} \leq s_{24} - c
s0≤s24−c
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;
}