---
# System prepended metadata

title: 經典網路流問題 Classic Flow Problems
tags: [內湖高中程式競賽]

---

# 經典網路流問題 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