# 經典網路流問題 Classic Flow Problems 很多人都說 flow 是在通靈,其實不是這樣。當你認真讀過幾本圖論、演算法、線性規劃跟計算理論的書之後,flow 根本只是小菜一碟。這篇算是網路流這個主題的集大成,會涵蓋 (幾乎) 所有競程常見的網路流問題,我會幫大家梳理 flow 的知識點 本篇會假設前面你都會了,在閱讀這篇之前,建議先讀過前面幾篇 : - [匹配與霍爾定理 Matching and Hall's Theorem](https://hackmd.io/@ShanC/Matching-and-Halls-Theorem) - [Kőnig 定理](https://hackmd.io/@ShanC/konig-theorem) - [擴充路徑演算法 Augmenting Path Algorithm](https://hackmd.io/@ShanC/Augmenting-Path-Algorithm) - [匈牙利演算法 Hungarian Algorithm](https://hackmd.io/@ShanC/Hungarian-Algorithm) - [連通性與 Menger 定理](https://hackmd.io/@ShanC/Menger-Theorem) - [最大流量-最小割定理 Max-flow Min-cut Theorem](https://hackmd.io/@ShanC/maxflow-mincut-theorem) - [二分圖匹配與網路流 Bipartite Matching and Flow](https://hackmd.io/@ShanC/bipartite-and-flow) - [Edmonds-Karp 演算法與 Dinic 演算法](https://hackmd.io/@ShanC/Edmonds-Karp-and-Dinic) ## 問題化約 Problem Reduction 計算機最重要的任務就是解問題。我們生活周遭有非常多問題,有些可計算,有些不可計算,但那不是今天的重點。有時我們會好奇問題要怎麼計算出來,卻苦苦想不到辦法來處理問題,或許我們可以考慮 problem reduction ### 解題機器 Problem Solver 「解題機器」$M$ 是一台抽象的機器 (||其實我想講的是圖靈機,只是怕有人聽不懂||),他可以代表一台計算機 (電腦)、一個程式或是一個演算法。一個可計算的問題通常是由實例 (instance) 與答案 (answer) 組成。$M$ 的作用就是可以把 instance 轉換成正確的 answer <center> <img src="https://hackmd.io/_uploads/rkUsoFCtex.png" style="margin: 0 auto; display: block; width: 400px"> </center> 但是在實務上,不是每個問題我們都可以找到相對應的 solver,所以可以考慮以下方法 ### 化約 Reduction 把一個問題 $A$ 轉換成另一個問題 $B$ 來解,這樣如果已知 $B$ 問題的 solver $M$,我們就能間接解決原本的問題。這叫做 reduction,是我們在搞演算法一個最重要的手法。Reduction 本質上就是一台機器 $M'$,專門把 $A$ 的 instance 轉換成 $B$ 的 instance <center> <img src="https://hackmd.io/_uploads/HkFvTFCKgg.png" style="margin: 0 auto; display: block; width: 400px"> </center> ### 舉例說明 舉一些在國高中就會遇到的例子 <center> <img src="https://hackmd.io/_uploads/Sk64z5Atle.png" style="margin: 0 auto; display: block; width: 400px"> </center> ### 最大流量問題化約圖 顯然問題互相轉換之下,也可以畫成一張龐大的圖。在這篇筆記中,我們會得到以下化約圖 <center> <img src="https://hackmd.io/_uploads/BJ1NmaQcll.png" style="margin: 0 auto; display: block; width: 400px"> </center> ## 最大流量問題 Maximum Flow Problem ### 問題簡述 在流量網路 $N$ 中,從 $s$ 到 $t$ 的最大流量為多少呢? ||詳細定義我就不再講了,有興趣自己去[複習](https://hackmd.io/@ShanC/maxflow-mincut-theorem)|| <center> <img src="https://hackmd.io/_uploads/rJceOqRFxe.png" style="margin: 0 auto; display: block; width: 400px"> </center> ### 解法 最大流量問題可以用[「Edmonds-Karp 演算法」或是「Dinic 演算法」](https://hackmd.io/@ShanC/Edmonds-Karp-and-Dinic)這兩種 solver 來解掉。這個問題可以說是本篇核心中的核心,所有的問題都會用**最大流量問題**的 solver 去解 ### 最小割問題 Minimum Cut Problem #### 問題簡述 在一張圖 $G$ 中,存在兩節點 $s, t\in V(G)$。請問要刪除多少邊才能使 $s$ 跟 $t$ 不連通? #### Reduction 要注意,**一般圖 $G$ 與流量網路 $N$ 是不同的數學物件**,中間必須經過轉換,不可以直接拿來講,許多教學都忽略這點 - 若 $G$ 是無向圖,就把它會為有向圖 (雙向) <center> <img src="https://hackmd.io/_uploads/Hys7ZjRtxl.png" style="margin: 0 auto; display: block; width: 400px"> </center> - 若 $G$ 是帶權邊的話,可以化為多重邊 <center> <img src="https://hackmd.io/_uploads/HJFQHWbgll.png" style="margin: 0 auto; display: block; width: 400px"> </center> 將兩點之間的「多重邊數量」化為「同一條邊上面的容量」後,根據[「最大流量-最小割定理」](https://hackmd.io/@ShanC/maxflow-mincut-theorem) : $$ \mathrm{最大流量}=\mathrm{最小割} $$ <center> <img src="https://hackmd.io/_uploads/Hyl_XX3skeg.png" style="margin: 0 auto; display: block; width:550px"> </center> 離開 $s$ 且流滿的邊無法形成 augmenting path,因此會形成最小割。如此一來,**最小割問題**就能夠化約成**最大流量問題** ### 小技巧 : 線性規劃 最大流問題的線性規劃可以寫作以下 : $$ \begin{aligned} \max\quad & \sum_{v\in V}f_{sv}-\sum_{v\in V}f_{vs}\\ \text{s.t.}\quad &f_{uv}\leq c(u, v)\quad\forall u, v\in V\\ &\sum_{v\in V} f_{vu}-\sum_{v\in V} f_{uv}=0\quad\forall u\in V-\{s, t\}\quad\\ &f_{uv}\geq0\quad\forall u, v\in V \end{aligned} $$ 有時候可以先把要最佳化的式子跟限制寫下來,幫助大腦整理資訊,然後去看看是否跟最大流問題很像,或許可以試試看。Dual 的東西我懶得寫,反正應該看得出來 ## 最大互斥邊路徑問題 Maximum Disjoint Paths Problem 有時候我不加「邊」這個字,但其實都一樣 ### 問題簡述 在一張圖 $G$ 中,存在兩節點 $s, t\in V(G)$。請問從 $s$ 到 $t$ 有幾條「邊」互不相同的路徑? <center> <img src="https://hackmd.io/_uploads/B1EvtoAtge.png" style="margin: 0 auto; display: block; width:550px"> </center> ### 解法 : 化約成最小割問題 Reduce to Min-cut Problem 根據 [Menger 定理](https://hackmd.io/@ShanC/Menger-Theorem) : $$ \mathrm{最小 (邊) 割}=\mathrm{最大互斥邊路徑數量} $$ <center> <img src="https://hackmd.io/_uploads/SJDSosRYel.png" style="margin: 0 auto; display: block; width:550px"> </center> 如此一來,**最大互斥邊路徑問題的 instance** 就原封不動就可以轉化成**最小割問題的 instance**。如果要進一步化成流量網路的話,只要把容量都設為 $1$ 即可 ### 點/邊 最大互斥邊路徑問題還有一個節點的版本叫作「最大互斥『點』路徑問題」,透過 [line graph](https://hackmd.io/@ShanC/Menger-Theorem#%E7%B7%9A%E5%9C%96-Line-Graph) $L(G)$,點的版本與邊的版本可以互相轉換,所以先把它們視為同一個問題 <center> <img src="https://hackmd.io/_uploads/ByRSCpj3Jg.png" style="margin: 0 auto; display: block; width: 550px"> </center> ### 同時取最長遞增子序列問題 #### 問題簡述 給一個存在最常遞增子序列的序列 $S$。若在不允許重複取同一元素的情況下,把每個最常遞增子序列取出,則最多會取出多少個子序列呢? <center> <img src="https://hackmd.io/_uploads/ryE7dKz9ge.png" style="margin: 0 auto; display: block; width: 500px"> </center> 如上圖,最多可以取出 $2$ 個最長遞增子序列 #### Reduction 在這個問題中,我們試圖把問題化約成最大互斥邊路徑問題 : 1. 先用 DP 求出「以第 $i$ 項為結尾時的 LIS 的長度」,並找出最長 LIS 為 $n$ 2. 建立一個虛擬的源點 $s$ 與虛擬的匯點 $t$ 3. 將每個元素 $i$ 拆分成兩個節點 $i$ 與 $i'$,並連邊 $i\to i'$ 4. 若 $dp[i]=1$,則 $s\to i$;若 $dp[i]=n$,則 $i'\to t$ <center> <img src="https://hackmd.io/_uploads/Hyz0Jnzqxx.png" style="margin: 0 auto; display: block; width: 500px"> </center> 如此一來,每個 LIS 都會代表一條 $s\to t$ 的路徑。因此,**同時取最長遞增子序列問題**就可以 reduce 成**最大互斥邊路徑問題**。如果要進一步化成流量網路的話,只要把容量都設為 $1$ 即可 <center> <img src="https://hackmd.io/_uploads/B18Henfqeg.png" style="margin: 0 auto; display: block; width: 500px"> </center> ## 最小花費最大流量問題 Min-cost Max-flow Problem 這個東西太長了,所以我們簡稱 MCMF 問題。也有人會簡稱 min-cost flow ### 問題簡述 給一張帶權重的流量網路 $N_w$,每一個單位流量經過邊 $e$ 時都需加上邊權 $w(e)$,請問若送出的流量為 $d$,則送達 $t$ 最小權重和為多少呢? 註 : 大多數時候會把邊的權重稱為「花費 (cost)」 ### 解法 : SPFA-flow 演算法 這在這篇有講過了,這裡就不多提,總之就是一個 MCMF 的 solver ### 小技巧 : 線性規劃 MCMF 的線性規劃可以寫成 : $$ \begin{aligned} \min\quad & \sum_{(u, v)\in E} w(u, v)\cdot f_{uv}\\ \text{s.t.}\quad &f_{uv}\leq c(u, v)\quad\forall u, v\in V\\ &\sum_{v\in V} f_{vu}-\sum_{v\in V} f_{uv}=0\quad\forall u\in V-\{s, t\}\quad\\ &\sum_{v\in V} f_{sv}-\sum_{v\in V} f_{vs}=d\\ &f_{uv}\geq0\quad\forall u, v\in V \end{aligned} $$ 有時候如果不知道題目怎麼解的話,可以把要最佳化的目標函數跟限制寫下來 (幫助大腦整理資訊)。如果長得像 MCMF 的線性規劃式子,那可以試試看 MCMF ### 最大流量問題化約成 MCMF 問題 把最大流量問題 instance 中的每條邊加上權重為 $0$ 就完成了 ### 最大花費流問題 Max-cost Flow Problem #### 問題簡述 給一張帶權重的流量網路 $N_w$,每一個單位流量經過邊 $e$ 時都需加上邊權 $w(e)$,請問若送出的流量為 $d$,則送達 $t$ 「最大」權重和為多少呢? #### Reduction 這很簡單,原因也很 trivial,只給方法不解釋 : 1. 把每條邊的花費加個負號 2. 跑 MCMF solver 3. 將答案取負號 ### 翻轉限制技巧 Flipping Constraints Technique 有時候我們會遇到一個問題 : 「同一條路可以走兩次,第一次可以拿到 $c$ 元,第二次沒錢拿,請問從起點走到終點最多可以達到多少錢?」像是這樣的問題,我們一樣可以用 MCMF 來解。以下圖為例 : <center> <img src="https://hackmd.io/_uploads/B1EXxazqlg.png" style="margin: 0 auto; display: block; width: 700px"> </center> 我們可以將邊拆成兩個 : - 一條邊容量為 $1$、花費為 $-c$ - 一條邊容量為 $\infty$、花費為 $0$ 這其實是透過最大流的「瓶頸」來達成的技巧,因為容量為 $1$ 的邊如果是 augmenting path 的一部分,那它下次就不可能再當一次 augmenting path (flow 是滿的)。之後走的邊肯定是容量無限的那條,而花費當然也只會是 $0$。這樣就可以跑 max-cost flow 囉 [深海機器人問題](https://www.luogu.com.cn/problem/P4012)是這個技巧的裸題,可以寫寫看 ## 二分圖最大權匹配問題 Maximum Weighted Matching Problem 注意 : 以下都是二分圖的問題,如果是一般圖不見得可以這樣解 (有些甚至沒辦法解) ### 問題簡述 如果我們把二分圖每條邊都給一個權重 $w:V\times V\rightarrow \mathbb{R^+}$。那麼最大權匹配總和是多少呢? ### 解法 : [匈牙利演算法 (KM 演算法)](https://hackmd.io/@ShanC/Hungarian-Algorithm) 在之前的章節有講過了,匈牙利演算法可以直接把二分圖最大權完美匹配問題解掉。如果是 $K_{n, m}$,$n \neq m$,那就加上「虛擬點」與「虛擬邊」,並將權重設為 $0$。因此這是將**二分圖最大權匹配問題**的 instance 化約成**二分圖最大權完美匹配問題**的 instance <center> <img src="https://hackmd.io/_uploads/rkBRVRAKxl.png" style="margin: 0 auto; display: block; width: 700px"> </center> ### 解法 : 化約成 MCMF 問題 Reduce to MCMF Problem 給定二分圖 $G=(U,V,E)$ 與邊權 $w(u,v)$ #### 建圖 - 建立源點 $s$ 與匯點 $t$ - 對每個 $u\in U$ : 加邊 $s \to u$,容量 $1$,費用 $0$ - 對每個 $v\in V$ : 加邊 $v \to t$,容量 $1$,費用 $0$ - 對每條 $(u,v)\in E$ : 加邊 $u \to v$,容量 $1$,費用 $-w(u,v)$ (因為我們要在最小費用下,等價於最大化總權重) #### 取解 - 路徑上用到的 $u\to v$ 邊 (流量為 1) 就是被選到的匹配邊 - 總費用最小 ⇔ 總權重最大 (因為費用設成 $-w$ 或 $C-w$) - 容量都設為 1,保證每個 $u$ 與 $v$ 最多被配一次 (約束匹配) - 送出一單位 $s\to u\to v\to t$ 的流,就等於選了邊 $(u,v)$ - 把邊費用設為 $-w$ (或 $C-w$),最小化總費用就會偏好高權重的邊,於是得到最大權 <center> <img src="https://hackmd.io/_uploads/Sk_Ekgy5ee.png" style="margin: 0 auto; display: block; width: 700px"> </center> 因此這算是**二分圖最大權匹配問題**的 instance 化約成 **MCMF 問題**的 instance ## 二分圖最大匹配問題 Bipartite Matching Problem ### 問題簡述 給一張二分圖,請問最大 (基數) 匹配為何? ### 解法 : [擴充路徑演算法](https://hackmd.io/@ShanC/Augmenting-Path-Algorithm) 根據 [Berge 定理](https://hackmd.io/@ShanC/Matching-and-Halls-Theorem#Berge-%E5%AE%9A%E7%90%86),「一個匹配 $M$ 是圖 $G$ 的最大匹配若且唯若 $G$ 沒有 $M$ 的擴充路徑」,**擴充路徑演算法**就是利用這個原理來跑。值得注意的是,這裡的擴充路徑 (增廣路徑) 與 flow 演算法所描述的是不同的東西,他們撞名了 <center> <img src="https://hackmd.io/_uploads/Syzc7doP1l.png" style="margin: 0 auto; display: block; width: 500px"> </center> <p class="text-center"> 圖源 : D.B.West - Introduction to Graph Theory 2/e p.113 </p> ### 解法 : [匈牙利演算法 (KM 演算法)](https://hackmd.io/@ShanC/Hungarian-Algorithm) 之前的章節有講過了,匈牙利演算法是用於求解最大權匹配問題,但是當我們把權重改成 $1$ 之後,就可以直接求解最大 (基數) 匹配了。因此我們這裡可以把它看作是**二分圖最大匹配問題**的 instance 化約成**二分圖最大權匹配問題**的 instance ### 解法 : 化約成最大流問題 Reduce to Max-flow Problem 我在[這篇](https://hackmd.io/@ShanC/bipartite-and-flow#%E6%9C%80%E5%A4%A7%E6%B5%81-%E6%9C%80%E5%B0%8F%E5%89%B2%E5%AE%9A%E7%90%86%E8%88%87-K%C5%91nig-Egerv%C3%A1ry-%E5%AE%9A%E7%90%86)講過並證明了[最大流-最小割定理](https://hackmd.io/@ShanC/maxflow-mincut-theorem)效力等價於 [Kőnig 定理](https://hackmd.io/@ShanC/konig-theorem)。事實上,**二分圖最大匹配問題**的 instance 化約成**最大流問題**的 instance 也是一模一樣。因此細節就不再多提了 <center> <img src="https://hackmd.io/_uploads/Bk2R6AWelx.png" style="margin: 0 auto; display: block; width: 700px"> </center> 如此化約之後,就可以得到結論 : $$ G\mathrm{的最大匹配}=N\mathrm{的最大流量} $$ ### 二分圖最小點覆蓋問題 Minimum Vertex Cover Problem #### 問題簡述 一張二分圖 $G$ 最少需要幾個節點才能覆蓋所有邊呢? #### Reduction 根據 [Kőnig 定理](https://hackmd.io/@ShanC/konig-theorem) : $\alpha'(G)=\beta(G)$ 因此找出二分圖最大匹配就可以找出二分圖最小點覆蓋 <center> <img src="https://hackmd.io/_uploads/BJ8OVEsPJe.png" style="margin: 0 auto; display: block; width: 250px"> </center> ### 二分圖最小權點覆蓋問題 Minimum Weighted Covering Problem #### 問題簡述 一張帶點權的二分圖 $G$ 最少挑哪些點才能覆蓋所有邊並且權重總和最小? #### Reduction 雖然有 [Egerváry 定理](https://hackmd.io/@ShanC/Hungarian-Algorithm#Egerv%C3%A1ry-%E5%AE%9A%E7%90%86)「$\mathrm{二分圖最大權匹配}=\mathrm{二分圖最小權}$」,但是這個定理講的比較像是一個「浮動」的點權,因此匈牙利算法解不出來。正確做法是 reduce 成**最小割問題** 1. 給定二分圖 $G=(U,V,E)$,點權 $w(x)\ge 0$ - 新增源點 $s$、匯點 $t$(有向網路) - 對每個 $u\in U$:加邊 $s\to u$,容量為 $w(u)$ - 對每個 $v\in V$:加邊 $v\to t$,容量為 $w(v)$ - 對每條 $(u,v)\in E$:加邊 $u\to v$,容量為 $\infty$ 2. 求一個最小 $s$–$t\text{ cut}$,得到分割 $[S,T]$($s\in S, t\in T$) 3. 把點覆蓋取為 $C \;=\; (U\cap T)\;\cup\; (V\cap S)$ 就是答案 <center> <img src="https://hackmd.io/_uploads/SJ6BxJflxe.png" style="margin: 0 auto; display: block; width: 520px"> </center> 如此一來就成功把**二分圖最小權點覆蓋問題**化約成**最小割問題**。當然我們也可以把**二分圖最小點覆蓋問題** reduce 成這個問題 ### 二分圖最小邊覆蓋問題 Minimum Edge Cover Problem #### 問題簡述 一張二分圖 $G$,最少需要幾條邊才能覆蓋所有節點呢? #### Reduction 根據[定理 [Gallai 1959]](https://hackmd.io/@ShanC/konig-theorem#%E5%AE%9A%E7%90%86-1-Gallai-1959) 可得「所有點的數量 $-$ 最大匹配數$=$最小邊覆蓋」 ### 二分圖最大獨立集問題 Maximum Independent Set Problem #### 問題簡述 一張二分圖 $G$ 的最大獨立集有多大呢? #### Reduction 根據[引理](https://hackmd.io/@ShanC/konig-theorem#%E5%BC%95%E7%90%86-1)可得「所有點的數量減去最小點覆蓋數$=$最大獨立集大小」 ### 二分圖最小花費 (基數) 匹配問題 Min-cost Bipartite Matching Problem #### 問題簡述 在一個帶花費的二分圖,每次把一條邊 $e$ 加進匹配中就需要加上該邊花費 $c(e)$,請問最大匹配的最小花費為何? #### Reduction 要注意這**不是**最大權匹配,兩者區別很大別搞混。考慮以下建圖方式 : 1. 在二分圖的每一條邊 $e$ 容量為 $\infty$、花費為 $c(e)$ 2. 將左邊的獨立集的所有節點 $u$ 建一條邊 $s\to u$ 3. 將右邊的獨立集的所有節點 $v$ 建一條邊 $v\to t$ 如此一來就將**二分圖最小花費 (基數) 匹配問題** reduce 成 MCMF 問題 ### 二分圖容量匹配問題 B-matching Problem #### 問題簡述 給一張二分圖 $G=(U, V, E)$,允許每個節點 $v$ 可以最多匹配 $b(v)$ 條邊,求最大匹配 <center> <img src="https://hackmd.io/_uploads/SJVkhm1qlx.png" style="margin: 0 auto; display: block; width: 520px"> </center> 上圖的實線就是一組合法解 #### Reduction 我們把問題轉換成最小割 : 1. 在二分圖中,從源點 $s$ 連到 $u\in U$,權重設為 $b(u)$ 2. 從 $v\in V$ 連到匯點 $t$,權重設為 $b(v)$ 3. $u\to v$ 的邊權重設為 $1$ 4. 最小割結果對應到最大 b-matching 出現在 $[U, V]$ 的最小割就是匹配邊 <center> <img src="https://hackmd.io/_uploads/S1I_0mJ9lx.png" style="margin: 0 auto; display: block; width: 520px"> </center> 如此一來,就可以把 **b-matching 問題**化約為**最小割問題** ## 網格化約技巧 Reduction on Grid 網格是我們在玩圖論問題時很常遇到的題型,通常這種題目都有特定的建圖技巧 ### 奇偶建圖 把每個格子編號 (如下圖),相鄰的格子肯定是奇偶相鄰。我們將這種建圖方式稱為奇偶建圖 <center> <img src="https://hackmd.io/_uploads/B1XnHRatex.png" style="margin: 0 auto; display: block; width: 400px"> </center> #### 引入問題 : [方格取數問題](https://www.luogu.com.cn/problem/P2774) 有一個 $m$ 行 $n$ 列的網格圖,每個方格中都有一個正整數。現要從方格中取數,使任兩個數所在方格沒有公共邊,且取出的數的總和最大,請求出最大的和 #### 輸入 ``` 3 3 1 2 3 3 2 3 2 3 1 ``` #### 輸出 ``` 11 ``` #### 題解 藉由上面的範測,我們來觀察一下 1. 當我們把這張圖相鄰的格子連起來,顯然這張圖是一張二分圖,因為奇數點無法與奇數點相鄰、偶數點無法與偶數點相鄰 <center> <img src="https://hackmd.io/_uploads/BJIRjAptlx.png" style="margin: 0 auto; display: block; width: 400px"> </center> 2. 說到二分圖,我們最先想到的就是最小割。在這一題,我們需要「取節點」,而不是「取邊」。所以顯然在弄成 flow 圖之後,我們想割的邊就是直接連至 $s$ / $t$ 的邊。因此其餘的邊我們一律寫上容量為 $\infty$ <center> <img src="https://hackmd.io/_uploads/H13lR0TFll.png" style="margin: 0 auto; display: block; width: 400px"> </center> 3. 現在只剩容量還沒處理好,基本上就是把原本的點權放上去就好。注意到我們在這張圖所求的是「最小割」,如果要求出最大,那就需要用總和去扣掉最小 <center> <img src="https://hackmd.io/_uploads/rkwJeyAFgg.png" style="margin: 0 auto; display: block; width: 400px"> </center> 最後來畫畫 $s$-$t\text{ cut}$,驗證一下正不正確 <center> <img src="https://hackmd.io/_uploads/HJDE-JAKxe.png" style="margin: 0 auto; display: block; width: 400px"> </center> 算一下,$\mathrm{cut}(s, t)=1+3+2+2+1=9$,$\mathrm{總和}-\mathrm{cut}(s, t)=20-9=11$ 正確答案 所以這其實是使用**二分圖最小權點覆蓋**來解**方格取數問題** #### 備註 其實這題也可以說是**二分圖帶權最大獨立集問題**,只是剛好定理 $n(G)-\beta(G)=\alpha(G)$ 放在帶權圖也適用,因此也可以看成是**二分圖最小權點覆蓋** #### 實作程式碼 - `grid[i][j]` : 第 $i$ 列第 $j$ 行的正整數 - `idx[i][j]` : 第 $i$ 列第 $j$ 行的節點編號 - `total` : 所有數字的總和 - `dir` : 方位 ```cpp /* 略 */ s = 0, t = n * m + 2, cnt = 0; flow.init(n * m + 3, s, t); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { num[i][j] = ++cnt; total += grid[i][j]; if ((i + j) % 2) // odd flow.add_edge(num[i][j], t, grid[i][j]); else // even flow.add_edge(s, num[i][j], grid[i][j]); } } for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { for (int k = 0; k < 4 && (i + j) % 2 == 0; k++) { // 相鄰的建邊,偶連到奇 int dx = i + dir[k][0], dy = j + dir[k][1]; flow.add_edge(num[i][j], num[dx][dy], INF); } } } cout << total - flow.flow() << '\n'; /* 略 */ ``` ### 行列建圖 有時後我們會希望可以表達「網格上一個方格 $(i, j)$ 可以跳到同一行 $i$ 的任一格或同一列 $j$ 的任一格」可以利用這個技巧 #### 引入問題 : [青蛙跳格子問題](https://atcoder.jp/contests/arc074/tasks/arc074_d?lang=en) 有一隻青蛙在 $n\times m$ 網格中的格子 $S$,青蛙每次移動都可以跳到同一行或同一列的荷葉上,最後可以跳到格子 $T$。請問最少需要拿掉幾片荷葉才能使青蛙無法跳到 $T$ ? #### 輸入 `'o'` 代表荷葉、`'.'` 代表沒東西 ``` 3 3 S.o .o. o.T ``` #### 輸出 ``` 2 ``` #### 題解 一樣我們先觀察圖,試著用最直觀的方式畫畫看 <center> <img src="https://hackmd.io/_uploads/SJtZ2_AKxe.png" style="margin: 0 auto; display: block; width: 500px"> </center> 感覺好像可以直接建圖,我們再多加一片荷葉看看 <center> <img src="https://hackmd.io/_uploads/SkMta_0Fll.png" style="margin: 0 auto; display: block; width: 500px"> </center> 不難注意到 : 1. 這張圖是無向圖 (兩方向皆可),可能會有很多環 2. 每一行要花 $O(n^2)$ 的時間建圖;每一列要花 $O(m^2)$ 的時間建圖 3. $S$ 會有入點、$T$ 會有出點 如果你熟悉 Dinic 會知道,這種方式跑一定會爆,所以顯然這不是個好方法 注意到每個在第 $i$ 行的節點都可以互相往來;每個第 $j$ 列的節點都可以互相往來。利用這一性質,我們可以造一些行與列「虛擬點」。考慮以下步驟 : 1. 我們先將虛擬點編號 <center> <img src="https://hackmd.io/_uploads/r1yrbYAFxx.png" style="margin: 0 auto; display: block; width: 500px"> </center> 2. 把虛擬點在出來後,若存在一片荷葉在 $(i, j)$,就將第 $i$ 行的虛擬點與第 $j$ 列的虛擬點相連 (這邊先看成是無向圖)。 <center> <img src="https://hackmd.io/_uploads/SkCibOVqee.png" style="margin: 0 auto; display: block; width: 500px"> </center> 3. 我們把源點 $S$ 跟匯點 $T$ 都加上去。注意前面連的都是無向邊 (雙向都要連),$S$ / $T$ 連的是有向邊 <center> <img src="https://hackmd.io/_uploads/rk1Mz_4cxl.png" style="margin: 0 auto; display: block; width: 500px"> </center> 4. 每片荷葉都代表一條邊,所以我們要求的其實是此圖的最小邊割,容量設為 $1$。由於不希望割到荷葉以外的邊,與 $S$ / $T$ 相連的邊容量都設為 $\infty$ <center> <img src="https://hackmd.io/_uploads/SkNVMdVqgg.png" style="margin: 0 auto; display: block; width: 500px"> </center> 如此一來我們就可以用**最小割問題**的 solver 來解掉**青蛙跳格子問題** #### 實作程式碼 ```cpp /* 略 */ int s = 0, t = n + m + 2; flow.init(n + m + 5, s, t); for (int i = 1; i <= n; i++) { // 1-based for (int j = n + 1; j <= m + n; j++) { // 注意 : 編號不可與 i 重複 char c; cin >> c; if (c == 'S') { flow.add_edge(s, i, INF); flow.add_edge(s, j, INF); } else if (c == 'T') { flow.add_edge(i, t, INF); flow.add_edge(j, t, INF); } else if (c == 'o') { flow.add_edge(i, j, 1); flow.add_edge(j, i, 1); } } } int ans = flow.flow(); cout << (ans >= INF ? -1 : ans) << '\n'; /* 略 */ ``` 像是在前幾篇的[這題](https://hackmd.io/@ShanC/Augmenting-Path-Algorithm#%E4%BE%8B%E9%A1%8C%E8%AA%AA%E6%98%8E)寫然也是這種建圖方法 ## 最小路徑覆蓋問題 Minimum Paths Cover Problem ### 問題簡述 給定一張有向無環圖 $G$,請問最少需要幾條路徑,才可以使得每個點都剛好在一條路徑上? <center> <img src="https://hackmd.io/_uploads/S1zQdEy5gx.png" style="margin: 0 auto; display: block; width: 500px"> </center> 如上圖,答案是 $2$ 條路徑 ### 解法 : 化約成二分圖最大匹配問題 Reduce to Bipartite Maximum Matching Problem 因為其他網路上的資料太過通靈,因此我這邊提出比較合理的看法 給一張節點數為 $n$ 的有向無環圖 $D$ 1. 由於這是有向無環圖 $D$,所形成的路徑肯定每個節點都只有一條出邊,僅最後一點沒有,因此得到 $$ \mathrm{覆蓋路徑數量}=\mathrm{沒有出邊的節點數} $$ <center> <img src="https://hackmd.io/_uploads/Sy_0-Hyqxe.png" style="margin: 0 auto; display: block; width: 500px"> </center> 2. 從有向圖的邊定義來看,每條邊 $(v, v')$ 都是 $V\times V$ 裡的元素。若我們把兩個 $V$,當作是不同的集合 $V$ 與 $V'$,因為同一個集合裡的元素不會連邊,兩集合分別都是獨立集。這個 $D$ 中由邊的定義所形成 $V$ 與 $V'$ 很自然地會形成二分圖 注意 : 因為這是有向圖,$v\to u$ 不代表 $u\to v$,因此在二分圖中只會連 $(v, u)$ 而不會連 $(u, v)$ <center> <img src="https://hackmd.io/_uploads/By5VMr19gx.png" style="margin: 0 auto; display: block; width: 500px"> </center> 3. 顯然 $D$ 中每條覆蓋路徑上的邊都會對應到二分圖的一條邊。因此 : $$ \mathrm{最少路徑覆蓋的「邊數」}=\mathrm{二分圖最大匹配} $$ 從 1. 得到 : $$ \mathrm{覆蓋路徑數量}=\mathrm{沒有出邊的節點數} $$ 又因為發現路徑覆蓋中沒有出邊的端點,當然也不會匹配到任何東西,所以 : $$ \mathrm{「最少」沒有出邊的節點數}=n-\mathrm{二分圖最大匹配} $$ 故 $$ \mathrm{「最少」覆蓋路徑數量}=n-\mathrm{二分圖最大匹配} $$ 如此一來**最小路徑覆蓋問題**就可以被 reduce 成**二分圖最大匹配問題** ## 專案選擇問題 Project Selection Problem 這問題有名到連維基百科都查得到,有時會看到簡稱 PSP 來表示此問題 ### 問題簡述 給你 $N$ 個專案 $E=\{E_1,\cdots, E_N\}$ 與 $M$ 台機器 $I=\{I_1,\cdots,I_M\}$,如果你執行了專案 $E_i$ 就可以拿到 $p_i$ 元,如果你買了機器 $I_j$ 就需花費 $q_j$ 元。已知專案 $i$ 需要用到的機器為 $R_i\subseteq I$,且兩個專案可以用同一台機器 (不用重複購買),你可以選擇執行哪些專案。請問怎麼選擇才會使收益最大? 註 : 其實就是求在符合上述規則的情況下 $$ \max\sum p_i-\sum q_j $$ ### 解法 : 化約成最小割問題 Reduce to Min-cut Problem 我們先建圖再來觀察 #### 建圖 1. 所有 $E_i$ 連邊 $s\to E_i$,容量為 $p_i$ 2. 所有 $I_j$ 連邊 $I_j\to t$,容量為 $q_j$ 3. 所有 $R_i$ 中,所有 $r\in R_i$ 連邊 $E_i\to r$,容量為 $\infty$ #### 觀察 以下圖為例,這是一個有兩個專案與四台機器的例子,機器沒有重複使用,建好圖會長這樣 : <center> <img src="https://hackmd.io/_uploads/SJAnmCz9eg.png" style="margin: 0 auto; display: block; width: 500px"> </center> 顯然可以看到 - 若執行 $E_1$,則會虧損 $20$ 元 - 若執行 $E_2$,則會賺 $52$ 元 觀察最小割的位置,都會割在「瓶頸」的邊上,也就是錢比較少的地方 <center> <img src="https://hackmd.io/_uploads/SyF2BRG9ge.png" style="margin: 0 auto; display: block; width: 500px"> </center> 根據上述觀察,不難發現 : - 若割在 $s\to E_i$ 的地方,則收入 $<$ 支出 - 若割在 $I_j\to t$ 的地方,則收入 $>$ 支出 結論 : 若我們只挑「收入 $>$ 支出」的專案,則會有最大收益 #### 求解 先設一些變數,等等方便推導 : - $a$ : 會賺錢的專案所拿到的收入 - $b$ : 不會賺錢的專案所拿到的收入 - $c$ : 會賺錢的專案會使用的機器支出 - $d$ : 不會賺錢的專案會使用的機器支出 - $m$ : min-cut 的邊權總和,就是 $b+c$ - $a+b=\sum p_i$ 我們要求的「最大收益」為 $a-c$ $$ \begin{aligned} a-c&=a+(b-b)-c\\ &=(a+b)-(b+c)\\ &=a+b-m\\ &=\sum p_i-m \end{aligned} $$ 所以 $\sum p_i-m$ 就是所求的最大收益 [這題](https://www.luogu.com.cn/problem/P2762)算是裸題可以寫寫看 #### 舉例說明 上面的例子是沒有用到同一個機器,來舉個需要用到同一台機器的例子 : - 專案 : $E=\{E_1, E_2\},\quad p_1=10,\quad p_2=25$ - 機器 : $I=\{I_1, I_2, I_3\},\quad q_1=5,\quad q_2=6,\quad q_3=7$ - $R_1=\{I_1, I_2\},\quad R_2=\{I_2, I_3\}$ 可以這樣建圖 : <center> <img src="https://hackmd.io/_uploads/H1b3vhXqxl.png" style="margin: 0 auto; display: block; width: 500px"> </center> 不難發現最小割的位置都是在機器的地方。若想求解,我們可以算 : - $\sum p_i=10+25=35$ - $m=5+6+7=18$ - $\text{最大收益}=\sum p_i-m=35-18=17$ ### 拓展問題 : $B$ 專案執行完才能執行 $A$ 專案 #### 問題簡述 同樣是專案選擇問題,但我們來多加一些限制 : 「$B$ 專案執行完才能執行 $A$ 專案,且 $A$ 專案不需要任何機器」 #### Reduction 我們可以考慮加一條容量為 $\infty$ 的邊 $A\to B$,其餘都與前一問題相同,如此一來 : - 邊 $A\to B$ 絕對不會是最小割 - 如果一個單位 flow 要流過 $A$,那一定也要流過 $B$ 才能到達匯點 $s$ #### 拓展 同樣的事也可以發生在機器。其實不難看出這樣的拓展已經跟誰是專案、誰是機器沒什麼關係了,因為這個問題當中,我們只在乎「節點 $B$ 要『依靠』節點 $A$ 才能有流量 flow 過去」因此建容量 $\infty$ 的邊 $A\to B$ ### 最大權閉合子圖問題 Maximum Closure Problem #### 閉合子圖 給一個有向無環圖 $D$,閉合子圖 $C$ 是指其中的子圖,使得不存在邊從 $C$ 指向 $D\setminus C$。換句話說就是,存在一條邊 $u\to v$,若 $u\in C$ 則 $v\in C$ #### 問題簡述 給一個帶點權的有向無環圖 $D$,請問最大權的閉合子圖權重和為多少呢? #### Reduction 將 $D$ 的所有邊加上 $\infty$ 的容量。若點權為正,則從 $s$ 連到該點;若點權為負,則從該點連到 $t$ <center> <img src="https://hackmd.io/_uploads/SyqBlamclg.png" style="margin: 0 auto; display: block; width: 500px"> </center> 可以把點權 $>0$ 的邊當作是收入、點權 $<0$ 的邊當作是支出,剩下的邊只是他們的「依靠」關係。這樣就 reduce 完了 ### 備註 : 如何分辨專案選擇問題? 如果遇到以下幾點,可以試著把題目轉乘專案選擇問題 / 最大權閉合子圖問題 : - 有明確的「收入」與「支出」 - 最大化收益 - $A$ 必須在 $B$ 完成之後才能做 ## 題目練習 [CSES Download Speed](https://cses.fi/problemset/task/1694) (flow 裸題) [CSES Police Chase](https://cses.fi/problemset/task/1695/) (割裸題) [CSES Task Assignment](https://cses.fi/problemset/task/2129) (完美匹配裸題) [CSES School Dance](https://cses.fi/problemset/task/1696) (匹配裸題) [Codeforces 653D. Delivery Bears](https://codeforces.com/problemset/problem/653/D) [CSES Grid Puzzle I](https://cses.fi/problemset/task/2432) (b-matching 裸題) [CSES Grid Puzzle II](https://cses.fi/problemset/task/2131) (帶權 b-matching 裸題) [CSES Distinct Routes](https://cses.fi/problemset/task/1711/) (Menger 定理裸題) [CSES Distinct Routes II](https://cses.fi/problemset/task/2130/) (帶權 Menger 定理裸題) [CSES Parcel Delivery](https://cses.fi/problemset/task/2121) (MCMF 裸題) [AtCoder Regular Contest 074F - Lotus Leaves](https://atcoder.jp/contests/arc074/tasks/arc074_d?lang=en) (行列建圖裸題) [AtCoder Beginner Contest 259G - Grid Card Game](https://atcoder.jp/contests/abc259/tasks/abc259_g) (把不要的割掉) [AtCoder Beginner Contest 224H - Security Camera 2](https://atcoder.jp/contests/abc224/tasks/abc224_h) (二分圖最大費用匹配) [AtCoder Beginner Contest 274G - Security Camera 3](https://atcoder.jp/contests/abc274/tasks/abc274_g) (最小點覆蓋) [AtCoder Regular Contest 085E - MUL](https://atcoder.jp/contests/arc085/tasks/arc085_c) (半裸的拓展專案選擇) [Codeforces 498C. Array and Operations](https://codeforces.com/problemset/problem/498/C) [2017ICPC World Finals - C Mission Improbable](https://codeforces.com/gym/101471/attachments) (假設格子都拿完,目標就變成放回,要怎麼放呢?) [Codeforces 1082G. Petya and Graph](https://codeforces.com/problemset/problem/1082/G) (誰是收入誰是支出? 看清楚就是裸題) [2015ICPC 西安 - C The Problem Needs 3D Arrays](https://codeforces.com/gym/100548/attachments) (最大稠密子圖,這篇沒講到) [BAPC14 - A Avoiding the Apocalypse](https://codeforces.com/gym/101512/attachments) (對時間做拆點分層) [2018 ACM-ICPC, Universidad Nacional de Colombia - F. UN Finals](https://codeforces.com/gym/101845/problem/F) [AtCoder Beginner Contest 225G - X](https://atcoder.jp/contests/abc225/tasks/abc225_g) (||專案選擇/閉合子圖||) [AtCoder Beginner Contest 193F - Zebraness](https://atcoder.jp/contests/abc193/tasks/abc193_f) (最小割=選擇的那對,要做一些轉換) [AtCoder Regular Contest 142E - Pairing Wizards](https://atcoder.jp/contests/arc142/tasks/arc142_e) (這題的建點很妙) [AtCoder Beginner Contest 247G - Dream Team](https://atcoder.jp/contests/abc247/tasks/abc247_g) (這題要想想你的模板在做什麼) [AtCoder Beginner Contest 421G - Increase to make it Increasing](https://atcoder.jp/contests/abc421/tasks/abc421_g) ---- ## 參考資料 - D.B.West - Introduction to Graph Theory 2/e - CLRS - Introduction to Algorithms, Fourth Edition - [[Tutorial] Maximum Flow and Minimum Cost Maximum Flow (to prove greedy)](https://codeforces.com/blog/entry/110328) - [Solving Problems with Min Cut Max Flow Duality](https://codeforces.com/blog/entry/136761) - [台師大演算法筆記 - matching](https://web.ntnu.edu.tw/~algo/Matching2.html) - [海大競程 - Classic Flow Problems](https://hackmd.io/@LeeShoWhaodian/ClassicFlowProblems#/) - [海大競程 - 匹配與網路流](https://hackmd.io/@LeeShoWhaodian/2024flow#/) - [[Tutorial, Flows] Project Selection Problem](https://codeforces.com/blog/entry/101354) - [Project Selection Problem for k-ary variables (Burn and Bury Problem)](https://shenpengfii.github.io/programming/algorithm/k-ary%20PSP) - [Wikipedia - Maximum flow problem](https://en.wikipedia.org/wiki/Maximum_flow_problem) - [Wikipedia - Minimum-cost flow problem](https://en.wikipedia.org/wiki/Minimum-cost_flow_problem#Minimum_weight_bipartite_matching) ---- > [ShanC 程式競賽筆記](https://hackmd.io/@ShanC/B1ouGxqcC) > 作者: ShanC > 更新: 2025/9/2