计蒜客 – 蒜头君的银行卡 | 算尽天下系列第 10 期 | SPFA – 差分约束系统
上一期,长老向大家分享了一个跟 BFS 很像的、可以求解负环的单源最短路算法 SPFA,今天,让我们来看一下 SPFA 在求解差分约束系统时的力量吧。
如果一个不等式组由
差分约束系统是求解一组变量的不等式组的算法,是最短路的一类经典应用。
为什么说它是最短路的一类经典应用呢?第一眼看过去难道不是一个数学问题吗?实际上,我们在求解差分约束系统时,可以将其转化为图论中单源最短路(或最长路)问题。
在求解最短路问题时,我们经常写的一个不等式是
因此我们可以将每个变量
<i, j>
。
连边有两种方式,第一种是连边后求最长路,第二种是连边后求最短路。
对于
我们再增加一个超级源
对这个图执行单源最短路算法,如果程序正常结束,那么得到的最短路答案数组 d[i]
就是满足条件的一组解;若图中存在负环,则该不等式组无解。
考虑到差分约束系统的边权可能为负,我们套用前面介绍的 SPFA 算法可解决这个问题。
在学习了使用 SPFA 求解差分约束系统之后,让我们来看一道例题,“计蒜客”的“蒜头君的银行卡”。
虽然蒜头君并没有多少钱,但是蒜头君办了很多张银行卡,共有
他只记得一些含糊的信息,这些信息主要以下列三种形式描述:
- 银行卡
比银行卡至少多元。
- 银行卡
比银行卡至多多元。
- 银行卡
和银行卡里的存款一样多。
但是由于蒜头君的记忆有些差,他想知道是否存在一种情况,使得银行卡的存款情况和他记忆中的所有信息吻合。
输入格式
第一行输入两个整数
接下来
- 如果每行第一个数是
,接下来有三个整数,表示银行卡比银行卡至少多元。
- 如果每行第一个数是
,接下来有三个整数,表示银行卡比银行卡至多多元。
- 如果每行第一个数是
,接下来有两个整数,表示银行卡和里的存款一样多。
3 3
3 1 2
1 1 3 1
2 2 3 2
输出格式
如果存在某种情况与蒜头君的记忆吻合,输出 Yes
,否则输出 No
。
Yes
这道题唯一的难点在于将不等式的表达形式转换成图中的边,一旦正确地插入了边,那么直接套用 SPFA 的模板就可以了,不需要做任何其他的修改。
我们先来分析一下
if (type == 3) {
// (a == b) 等价于 (a <= b && b <= a)
insert(a, b, 0);
insert(b, a, 0);
}
若
if (type == 1) {
// a 比 b 至少多 c 元:(a - b >= c) => (b - a <= -c) => (a + (-c) >= b),从 a 到 b 连一条权值为 -c 的边,求最短路
insert(a, b, -c);
} else {
// a 比 b 至多多 c 元:(a - b <= c) => (a - c <= b) => (b + c >= a),从 b 到 a 连一条权值为 c 的边,求最短路
insert(b, a, c);
}
在正确插入所有的边以后,加上一个超级源:
for (int i = 1; i <= n; i++) {
insert(0, i, 0); // 插入超级源,连向每一个点,权重为 0
}
并且在 SPFA 的模板中修改 cnt[v] == n
时的情况:
if (cnt[v] == n + 1) {
// 因为加入了超级源点,所以一共是 n + 1 个点
// 出现负环,不等式组无解
printf("No");
exit(0);
}
其余的部分均直接套用 SPFA 模板,模板代码可参见前一篇文章。
完整代码见:https://www.jxtxzzw.com/archives/5268[1]
参考资料
[1]
https://www.jxtxzzw.com/archives/5268: https://www.jxtxzzw.com/archives/5268