计蒜客 – 蒜头君的银行卡 | SPFA – 差分约束系统

计蒜客 – 蒜头君的银行卡 | 算尽天下系列第 10 期 | SPFA – 差分约束系统

上一期,长老向大家分享了一个跟 BFS 很像的、可以求解负环的单源最短路算法 SPFA,今天,让我们来看一下 SPFA 在求解差分约束系统时的力量吧。

如果一个不等式组由 n n n 个变量和 m m m 个约束条件组成,且每个约束条件都是形如 x i − x j ≤ k , 1 ≤ i , j ≤ n x_i-x_j\leq k,1\leq i,j\leq n xixjk,1i,jn 的不等式,则称其为差分约束系统 (system of difference constraints)。

差分约束系统是求解一组变量的不等式组的算法,是最短路的一类经典应用。

为什么说它是最短路的一类经典应用呢?第一眼看过去难道不是一个数学问题吗?实际上,我们在求解差分约束系统时,可以将其转化为图论中单源最短路(或最长路)问题。

在求解最短路问题时,我们经常写的一个不等式是 d u + w u , v ≥ d v d_u+w_{u,v} \geq d_v du+wu,vdv,移项可得 d v − d u ≤ w u , v d_v-d_u\leq w_{u,v} dvduwu,v,这类似于不等式组中的 x j − x i ≤ k x_j-x_i\leq k xjxik。所以我们可以理解成从顶点 u u u 到顶点 v v v 连一条权值为 w u , v w_{u,v} wu,v 的边,用最短路算法得到最短路的答案 d i d_i di,也就求出了原不等式组的一个解。

因此我们可以将每个变量 x i x_i xi 作为一个顶点,对于约束条件 x j − x i ≤ k x_j-x_i\leq k xjxik,连接一条边权为 k k k 的有向边 <i, j>

连边有两种方式,第一种是连边后求最长路,第二种是连边后求最短路。

对于 x j − x i ≤ k x_j - x_i \leq k xjxik,若求最长路,则变形为 x j − k ≤ x i x_j -k\leq x_i xjkxi,从 j j j i i i 连一条权值为 k k k 的边;若求最短路,则变形为 x i + k ≥ x j x_i+k\geq x_j xi+kxj,从 i i i j j j 连一条权值为 k k k 的边。

我们再增加一个超级源 s s s s s s 连向其余每个顶点,边权均为 0 0 0

对这个图执行单源最短路算法,如果程序正常结束,那么得到的最短路答案数组 d[i] 就是满足条件的一组解;若图中存在负环,则该不等式组无解。

考虑到差分约束系统的边权可能为负,我们套用前面介绍的 SPFA 算法可解决这个问题。

在学习了使用 SPFA 求解差分约束系统之后,让我们来看一道例题,“计蒜客”的“蒜头君的银行卡”。

虽然蒜头君并没有多少钱,但是蒜头君办了很多张银行卡,共有 n n n 张,以至于他自己都忘记了每张银行卡里有多少钱了。

他只记得一些含糊的信息,这些信息主要以下列三种形式描述:

  1. 银行卡 a a a 比银行卡 b b b 至少多 c c c 元。

  2. 银行卡 a a a 比银行卡 b b b 至多多 c c c 元。

  3. 银行卡 a a a 和银行卡 b b b 里的存款一样多。

但是由于蒜头君的记忆有些差,他想知道是否存在一种情况,使得银行卡的存款情况和他记忆中的所有信息吻合。

输入格式

第一行输入两个整数 n n n m m m,分别表示银行卡数目和蒜头君记忆中的信息的数目。 ( 1 ≤ n , m ≤ 10000 ) (1\leq n,m\leq 10000) (1n,m10000)

接下来 m m m 行:

  • 如果每行第一个数是 1 1 1,接下来有三个整数 a , b , c a, b, c a,b,c,表示银行卡 a a a 比银行卡 b b b 至少多 c c c 元。

  • 如果每行第一个数是 2 2 2,接下来有三个整数 a , b , c a, b, c a,b,c,表示银行卡 a a a 比银行卡 b b b 至多多 c c c 元。

  • 如果每行第一个数是 3 3 3,接下来有两个整数 a , b a, b a,b,表示银行卡 a a a b b b 里的存款一样多。 ( 1 ≤ n , m , a , b , c ≤ 10000 ) (1\leq n,m,a,b,c\leq 10000) (1n,m,a,b,c10000)

3 3
3 1 2
1 1 3 1
2 2 3 2

输出格式

如果存在某种情况与蒜头君的记忆吻合,输出 Yes,否则输出 No

Yes

这道题唯一的难点在于将不等式的表达形式转换成图中的边,一旦正确地插入了边,那么直接套用 SPFA 的模板就可以了,不需要做任何其他的修改。

我们先来分析一下 a a a b b b 中钱一样多的情况。若 a = b a = b a=b,则等价于 $a \leqslant b $ 且 $ b \leqslant a$,那么我们可以往图中插入一条从 a a a b b b 的权重为 0 0 0 的边,以及一条从 b b b a a a 的权重为 0 0 0 的边。

if (type == 3) {
	// (a == b) 等价于 (a <= b && b <= a)
	insert(a, b, 0);
	insert(b, a, 0);
}

a a a b b b 至少多 c c c 元,那么有 a − b ⩾ c a - b \geqslant c abc,移项得到 b − a ⩽ − c b - a \leqslant -c bac,即 a + ( − c ) ⩾ b a + (-c) \geqslant b a+(c)b,对照上面的公式可以发现,应该从 a a a b b b 连一条权值为 − c -c c 的边,求最短路;类似地,若 a a a b b b 至多多 c c c 元,那么 a − b ⩽ c a - b \leqslant c abc 可得 b + c > = a b + c >= a b+c>=a,从 b b b a a a 连一条权值为 c c c 的边,求最短路。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凝神长老

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值