計蒜客 – 蒜頭君的銀行卡 | SPFA – 差分約束系統

計蒜客 – 蒜頭君的銀行卡 | 算盡天下系列第 10 期 | SPFA – 差分約束系統

上一期,長老向大家分享了一個跟 BFS 很像的、可以求解負環的單源最短路算法 SPFA,今天,讓我們來看一下 SPFA 在求解差分約束系統時的力量吧。

如果一個不等式組由 nn 個變量和 mm 個約束條件組成,且每個約束條件都是形如 xixjk,1i,jnx_i-x_j\leq k,1\leq i,j\leq n 的不等式,則稱其爲差分約束系統 (system of difference constraints)。

差分約束系統是求解一組變量的不等式組的算法,是最短路的一類經典應用。

爲什麼說它是最短路的一類經典應用呢?第一眼看過去難道不是一個數學問題嗎?實際上,我們在求解差分約束系統時,可以將其轉化爲圖論中單源最短路(或最長路)問題。

在求解最短路問題時,我們經常寫的一個不等式是 du+wu,vdvd_u+w_{u,v} \geq d_v,移項可得 dvduwu,vd_v-d_u\leq w_{u,v},這類似於不等式組中的 xjxikx_j-x_i\leq k。所以我們可以理解成從頂點 uu 到頂點 vv 連一條權值爲 wu,vw_{u,v} 的邊,用最短路算法得到最短路的答案 did_i,也就求出了原不等式組的一個解。

因此我們可以將每個變量 xix_i 作爲一個頂點,對於約束條件 xjxikx_j-x_i\leq k,連接一條邊權爲 kk 的有向邊 <i, j>

連邊有兩種方式,第一種是連邊後求最長路,第二種是連邊後求最短路。

對於 xjxikx_j - x_i \leq k,若求最長路,則變形爲 xjkxix_j -k\leq x_i,從 jjii 連一條權值爲 kk 的邊;若求最短路,則變形爲 xi+kxjx_i+k\geq x_j,從 iijj 連一條權值爲 kk 的邊。

我們再增加一個超級源 ssss 連向其餘每個頂點,邊權均爲 00

對這個圖執行單源最短路算法,如果程序正常結束,那麼得到的最短路答案數組 d[i] 就是滿足條件的一組解;若圖中存在負環,則該不等式組無解。

考慮到差分約束系統的邊權可能爲負,我們套用前面介紹的 SPFA 算法可解決這個問題。

在學習了使用 SPFA 求解差分約束系統之後,讓我們來看一道例題,“計蒜客”的“蒜頭君的銀行卡”。

雖然蒜頭君並沒有多少錢,但是蒜頭君辦了很多張銀行卡,共有 nn 張,以至於他自己都忘記了每張銀行卡里有多少錢了。

他只記得一些含糊的信息,這些信息主要以下列三種形式描述:

  1. 銀行卡 aa 比銀行卡 bb 至少多 cc 元。

  2. 銀行卡 aa 比銀行卡 bb 至多多 cc 元。

  3. 銀行卡 aa 和銀行卡 bb 裏的存款一樣多。

但是由於蒜頭君的記憶有些差,他想知道是否存在一種情況,使得銀行卡的存款情況和他記憶中的所有信息吻合。

輸入格式

第一行輸入兩個整數 nnmm,分別表示銀行卡數目和蒜頭君記憶中的信息的數目。(1n,m10000)(1\leq n,m\leq 10000)

接下來 mm 行:

  • 如果每行第一個數是 11,接下來有三個整數 a,b,ca, b, c,表示銀行卡 aa 比銀行卡 bb 至少多 cc 元。

  • 如果每行第一個數是 22,接下來有三個整數 a,b,ca, b, c,表示銀行卡 aa 比銀行卡 bb 至多多 cc 元。

  • 如果每行第一個數是 33,接下來有兩個整數 a,ba, b,表示銀行卡 aabb 裏的存款一樣多。(1n,m,a,b,c10000)(1\leq n,m,a,b,c\leq 10000)

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

輸出格式

如果存在某種情況與蒜頭君的記憶吻合,輸出 Yes,否則輸出 No

Yes

這道題唯一的難點在於將不等式的表達形式轉換成圖中的邊,一旦正確地插入了邊,那麼直接套用 SPFA 的模板就可以了,不需要做任何其他的修改。

我們先來分析一下 aabb 中錢一樣多的情況。若 a=ba = b,則等價於 $a \leqslant b $ 且 $ b \leqslant a$,那麼我們可以往圖中插入一條從 aabb 的權重爲 00 的邊,以及一條從 bbaa 的權重爲 00 的邊。

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

aabb 至少多 cc 元,那麼有 abca - b \geqslant c,移項得到 bacb - a \leqslant -c,即 a+(c)ba + (-c) \geqslant b,對照上面的公式可以發現,應該從 aabb 連一條權值爲 c-c 的邊,求最短路;類似地,若 aabb 至多多 cc 元,那麼 abca - b \leqslant c 可得 b+c>=ab + c >= a,從 bbaa 連一條權值爲 cc 的邊,求最短路。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章