Flow

Lecturer: 22527 Brine

我是誰

22527 鄭竣陽
Brine
BrineTW#7355
  • 建中資訊參伍 學術長
  • 建中電研肆貳 學術
  • 為了排版走火入魔
  • 想學網路流所以當網路流講師
  • 大家的偶像
Index

有用的學習資源

Glossary

名詞定義

Flow

  • 什麼是「Flow」?
    • 一張很猛的 Warframe MOD
    • 人類一種完全沉浸(專注)和完全投入於活動本身的心智狀態
    • 不要瞎掰好嗎

Flow Network

  • 又稱 Capacity Graph(容量圖)
  • 一張帶權有向圖 \(G(V,E)\)
    • 每個邊有一非負權重 \(c(u, v) \ge 0\) 代表容量
      • \((u, v) \notin E \rightarrow c(u,v) = 0\)
0
1
2
5
3
4
0/5
0/4
0/5
0/2
0/3
0/6
0/2
0/1

s-t Flow

  • 有一入度為 \(0\) 之點 \(s\) 和一出度為 \(0\) 之點 \(t\)
2
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1
0
1
2
5
3
4

s-t Flow

  • 有一入度為 \(0\) 之點 \(s\) 和一出度為 \(0\) 之點 \(t\)
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

s-t Flow

  • 有一入度為 \(0\) 之點 \(s\) 和一出度為 \(0\) 之點 \(t\)
  • 定義 \(f(u, v)\) 為 \((u, v)\) 上的流函數,其滿足:
    • 流量限制:\(\forall (u, v) \in V \times V, f(u, v) \le c(u, v)\)
    • 流量對稱:\(\forall (u, v) \in V \times V, f(u, v) = -f(v, u)\)
    • 流量守恆:\(\forall u\in V - \{s, t\}, \displaystyle \sum_{v \in V} f(u, v) = 0\)
s
t
2/5
2/4
2/2
0/5
4/5
3/3
1/6
1/1
1
2
3
4

s-t Flow

  • 定義一個流函數的值 \(|f|\) 到匯點 \(t\) 的流量和
    • \(|f| = \displaystyle \sum_{u \in V} f(u, t)\)
s
t
2/5
2/4
2/2
0/5
4/5
3/3
1/6
1/1
1
2
3
4

s-t Flow

  • 定義一個流函數的值 \(|f|\) 到匯點 \(t\) 的流量和
    • \(|f| = \displaystyle \sum_{u \in V} f(u, t)\)
    • \(|f| = \displaystyle \sum_{v \in V} f(s, v)\)
s
t
2/5
2/4
2/2
0/5
4/5
3/3
1/6
1/1
1
2
3
4

s-t Cut

  • 什麼是割(cut)?
    • 將一張圖的點集 \(V\) 分為 \(V_a, V_b\)
      • \(V_a \cup V_b = V\)
      • \(V_a \cap V_b = \varnothing\)
    • 得到一個割集(cut-set)
      • \(E_{cut}\) 為跨越 \(V_a, V_b\) 的邊集

s-t Cut

  • 什麼是割(cut)?
    • 將一張圖的點集 \(V\) 分為 \(V_a, V_b\)
      • \(V_a \cup V_b = V\)
      • \(V_a \cap V_b = \varnothing\)
    • 得到一個割集(cut-set)
      • \(E_{cut}\) 為跨越 \(V_a, V_b\) 的邊集
0
1
3
2
4

s-t Cut

  • 什麼是割(cut)?
    • 將一張圖的點集 \(V\) 分為 \(V_a, V_b\)
      • \(V_a \cup V_b = V\)
      • \(V_a \cap V_b = \varnothing\)
    • 得到一個割集(cut-set)
      • \(E_{cut}\) 為跨越 \(V_a, V_b\) 的邊集
0
1
3
2
4

s-t Cut

  • 什麼是割(cut)?
    • 將一張圖的點集 \(V\) 分為 \(V_a, V_b\)
      • \(V_a \cup V_b = V\)
      • \(V_a \cap V_b = \varnothing\)
    • 得到一個割集(cut-set)
      • \(E_{cut}\) 為跨越 \(V_a, V_b\) 的邊集
0
1
3
2
4

s-t Cut

0
1
3
2
4
  • 那什麼是一個 s-t cut 呢?

s-t Cut

  • 那什麼是一個 s-t cut 呢?
0
1
3
2
4

s-t Cut

  • 那什麼是一個 s-t cut 呢?
    • 將容量圖之點集 \(V\) 分割成兩子點集
      • \(s, t\) 分別在其中一個,標記為 \(S, T\)
      • 記作 \(C(S,T)\)
    • \(C(S,T)\) 的值為所有從 \(S\) 到 \(T\) 的邊之權和
      • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)

s-t Cut

  • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

s-t Cut

  • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

s-t Cut

  • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

s-t Cut

  • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

s-t Cut

  • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)
  • \(\text{flow}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} f(u, v) - \displaystyle \sum_{u \in S} \sum_{v \in T} f(v, u)\)
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

s-t Cut

  • \(\text{cut}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)
  • \(\text{flow}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} f(u, v) - \displaystyle \sum_{u \in S} \sum_{v \in T} f(v, u)\)
s
t
1
2
3
4
4/5
2/4
1/2
3/5
3/5
2/3
4/6
1/1
Maximum Flow

最大流問題

最大流問題

  • 給定一張容量圖 \(G(V, E)\),求該圖上的最大流
  • 隨便流,流到不能流為止
    • 真的嗎?

最大 vs. 極大

  • 最大流 maximum flow
    • 若一個流 \(f\) 為最大,則不存在 \(f'\) 使得 \(|f'| > |f|\)
    • 沒有辦法以任何形式增加 \(|f|\)
  • 極大流 maximal flow
    • 若一個流 \(f\) 為極大,則不存在 \(f'\) 使得 \(|f'| > |f|\) 且:
      • \(\forall (u, v) \in V \times V, f'(u, v) \ge f(u, v)\)
    • 沒有辦法以當前的流為基礎下增加 \(|f|\) 
    • 蛤?

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
0/5
0/4
0/2
0/5
0/5
0/3
0/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
2/5
0/4
2/2
0/5
2/5
2/3
0/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
2/5
0/4
2/2
0/5
2/5
2/3
0/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
5/5
0/4
2/2
3/5
2/5
2/3
3/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
5/5
0/4
2/2
3/5
2/5
2/3
3/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
5/5
1/4
2/2
3/5
3/5
3/3
3/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
5/5
1/4
2/2
3/5
3/5
3/3
3/6
0/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
s
t
1
2
3
4
5/5
2/4
2/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這是一張很好用的流量圖
    • 我們假設我們的演算法會優先填入字典序最小的流
    • 這樣是最大流嗎?
      • 顯然否,但這是極大流
    • 哪裡笨笨的?
s
t
1
2
3
4
5/5
2/4
2/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
2/4
2/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
2/4
2/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
2/4
0/2
3/5
2/5
1/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
2/4
0/2
3/5
2/5
1/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
4/4
0/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
4/4
0/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
4/4
0/2
3/5
4/5
3/3
4/6
1/1

來看例子

  • 這段不要流過來!
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

來看例子

  • 這段不要流過來!
    • 這樣就是最大流了
  • 流量有改變的邊有哪些?
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

來看例子

  • 這段不要流過來!
    • 這樣就是最大流了
  • 流量有改變的邊有哪些?
    • 看起來像不像一條路徑?
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

新概念

  • 要怎麼找到一個很爛的 \(f(u, v)\) 並修改?
    • 好像很難去處理
    • 那不如我們把圖重畫!
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

逆流而上

  • 將一條邊的流 \(f(u, v)\) 減少,就像是讓水逆著流
    • 可以逆著流(取消)的量會是 \(f(u, v)\)
    • 可以正著流的量會是 \(c(u,v) - f(u, v)\)
    • 還需要記錄容量嗎?
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

剩餘圖

  • Residual Graph
    • 雖然我覺得很爛但我想不到更好的名字
  • 對於每個容量圖 \(G(V, E)\) 存在一個剩餘圖 \(G'(V', E')\)
    • \(\forall (u, v) \in E,\ (u, v) \in E' \land (v, u) \in E'\)
    • \(w(u,v) = c(u, v) - f(u, v), w(v,u) = f(u, v)\)
    • \(G'_f\) 為 \(G\) 的流函數為 \(f\) 時的剩餘圖
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

剩餘圖

  • Residual Graph
  • 對於每個容量圖 \(G(V, E)\) 存在一個剩餘圖 \(G'(V', E')\)
    • \(\forall (u, v) \in E,\ (u, v) \in E' \cap (v, u) \in E'\)
    • \(w(u,v) = c(u, v) - f(u, v), w(v,u) = f(u, v)\)
    • \(G'_f\) 為 \(G\) 的流函數為 \(f\) 時的剩餘圖
s
t
1
2
3
4
0
0
2
0
1
0
0
0
6
3
1
4
5
0
5
4

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
0
0
2
0
1
0
0
0
6
3
1
4
5
0
5
4

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
5
4
2
5
5
3
6
1
0
0
0
0
0
0
0
0

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
5
4
2
5
5
3
6
1

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
5
4
2
5
5
3
6
1

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
5
3
2
5
4
3
5
0
1
1
1
1

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
5
3
2
5
4
3
5
1
1
1
1

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
s
t
1
2
3
4
5
3
2
5
4
3
5
1
1
1
1

剩餘圖

  • 把邊權為零的邊視為不存在
  • 如此一來,只要在 \(G'_f\) 上找到一條從 \(s\) 到 \(t\) 的路徑 \(\mathcal P\) 就好了
  • 這些路徑被稱為擴增路徑(augmenting paths)
s
t
1
2
3
4
4
3
2
4
4
2
5
1
0
1
1
1
1
1
1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
s
t
1
2
3
4
5/5
4/4
0/2
5/5
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
s
t
1
2
3
4
5/5
4/4
0/2
5/9
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
s
t
1
2
3
4
5/5
4/4
0/2
5/9
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
s
t
1
2
3
4
5/5
4/8
0/2
5/9
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
s
t
1
2
3
4
5/5
4/8
0/2
5/9
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
s
t
1
2
3
4
5/5
4/8
0/2
5/9
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
s
t
1
2
3
4
5/5
4/8
0/2
5/9
4/5
3/3
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
s
t
1
2
3
4
5/5
4/8
0/2
5/9
4/5
3/5
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
s
t
1
2
3
4
5/5
4/8
0/2
5/9
4/5
3/5
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
s
t
1
2
3
4
5/5
5/8
0/2
5/9
5/5
4/5
6/6
1/1

🈹與流的關係

  • 我們再次回到這張圖
    • 把它隨便改改,看最大流會不會改變
    • 好像沒有變欸
    • 是什麼地方「堵住了
    • 割?!
s
t
1
2
3
4
5/5
5/8
0/2
5/9
5/5
4/5
6/6
1/1

🈹與流的關係

  • 一張圖的最大流 \(\max |f|\) 看起來跟最小割 \(\min \text{cut}(S,T)\) 相關
    • 真的嗎
    • 如果是,對於解決最大流問題有什麼用?
s
t
1
2
3
4
5/5
5/8
0/2
5/9
5/5
4/5
6/6
1/1
Max-Flow Min-Cut

最大流最小割定理

最大流最小割定理

  • 對於一個流量圖 \(G\) 及 \(G\) 的一個流函數 \(f\),以下三者等價:
    • \(|f|\) 為最大
    • \(G'_f\) 上沒有擴增路徑
    • 存在 \(\text{cut}(S,T) = |f|\)
  • 接下來我們會簡單的證明以下三項等價

容量限制

  • \(\text{flow}(S, T) \le \text{cut}(S,T)\)
    • 顯然
    • \(\text{flow}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} f(u, v) - \sum_{u \in S} \sum_{v \in T} f(v, u)\)
    • \(\forall (u, v) \in V \times V, f(u, v) \ge 0\)
    • \(\displaystyle \sum_{u \in S} \sum_{v \in T} f(u, v) - \sum_{u \in S} \sum_{v \in T} f(v, u) \le \sum_{u \in S} \sum_{v \in T} f(u, v)\)
    • \(\forall (u, v) \in V \times V, f(u, v) \le c(u,v)\)
    • \(\displaystyle \sum_{u \in S} \sum_{v \in T} f(u, v) \le \displaystyle \sum_{u \in S} \sum_{v \in T} c(u, v)\)

流量相等

  • 在 \(G\) 上流為 \(f\) 的任意 s-t cut,\(\text{flow}(S,T) = |f|\)
  • 將 \(v \in S - \{s\}\) 換到 \(T\),形成 \(S' = S - \{v\}, T' = T \cup \{v\}\)
  • \(\text{flow}(S, T) = \displaystyle \sum_{u \in S} \sum_{v \in T} f(u, v) - \sum_{u \in S} \sum_{v \in T} f(v, u)\)
  • \(\text{flow}(S', T') = \text{flow}(S, T) + \displaystyle \sum_{u \in S'} f(u, v) - \sum_{u \in T'} f(v, u)\)
  • \(\displaystyle \sum_{u \in S'} f(u, v) - \sum_{u \in T'} f(v, u) = \sum_{u \in S'} f(u, v) + \sum_{u \in T'} f(u, v)\)
    • \(\because\) 流量對稱
  • \(\displaystyle \sum_{u \in S'} f(u, v) + \sum_{u \in T'} f(u, v) = \sum_{u \in S' \cup T'} f(u, v) = 0\)
    • \(\because \forall v \notin \{s, t\}\) 流量守恆

流量相等,倒過來

  • 在 \(G\) 上流為 \(f\) 的任意 s-t cut,\(\text{flow}(S,T) = |f|\)
  • 將 \(v \in T - \{t\}\) 換到 \(S\),形成 \(S' = S \cup \{v\}, T' = T - \{v\}\)
  • 證明 \(\text{flow}(S', T') = \text{flow}(S, T)\)
  • \(\text{The proof is left as an exercise for the reader.}\)

綜合以上兩點

  • 對於任意點的 s-t cut \(C(S, T)\)
    • \(\text{flow}(S,T) \le \text{cut}(S,T)\)
    • \(|f| = \text{flow}(S,T)\)
  • \(|f| \le \text{cut}(S,T)\)

真.熱血證明

  • 回顧一下,MFMC 定理宣稱以下三者等價:
    • \(|f|\) 為最大
    • \(G_f'\) 上沒有擴增路徑
    • 存在 \(\text{cut}(S,T) = |f|\)
  • 使三個點屬於同一個強連通分量至少需要幾條邊?
    • 三個!
    • 所以我們將分三步證明以下三者等價

最大流無法擴增

  • \((|f| \text{ is maximum}) \rightarrow (G'_f \text{ has no augmenting path})\)
  • \(p \rightarrow q \implies \lnot q \rightarrow \lnot p\)
  • \((G'_f \text{ has augmenting paths}) \rightarrow (|f| \text{ is not maximum})\)
  • 根據定義,擴增路徑 \(\mathcal P\) 上的最小權大於零
  • 故採取擴增路徑必然使 \(|f|\) 上升
  • 敘述成立

有割等於流

  • 這裡要證明若 \(G_f'\) 上沒有擴增路徑則存在 \(\text{cut}(S,T) = |f|\)
  • 我們來構造一個 \(C(S,T)\) 使得 \(\text{cut}(S,T) = |f|\) 吧 
    • 由於 \(G'_f\) 沒有擴增路徑,必定有點和 \(s\) 不連通(如 \(t\))
    • 令和 \(s\) 連通的點屬於 \(S\),否則屬於 \(T\)
    • 由於 \(S \not = \varnothing \cap T \not = \varnothing\),\(C(S,T)\) 是一個合法的 s-t cut
    • \(\text{flow}(S,T)\) 會是多少?
  • 根據流量相等,\(\text{flow}(S,T) = |f|\)!

有割即是最大流

  • 令 \(\text{cut}(S,T) = |f|\) 
  • 由於 \(|f| \le \text{cut}(S,T)\),敘述成立
  • 至此證明三者等價
  • 由於有 \(f\) 使得 \(\text{cut}(S,T) = |f|\),\(\min \text{cut}(S, T) = \max |f|\)
Max Flow Algorithm

最大流演算法

問了就是唬爛

Ford-Fulkerson

  • 根據 MFMC 定理,若還有擴增路徑,則 \(|f|\) 非最大
  • 聰明如你,一定會想到怎麼做
  • 一直找擴增路徑,直到沒有為止
    • 對,就這樣
  • 複雜度長怎樣

複雜度分析

複雜度分析

s
t
1
2
0/1
0/225
0/225
0/225
0/225
  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?

複雜度分析

s
t
1
2
0/1
0/225
0/225
0/225
0/225
  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?

複雜度分析

s
t
1
2
1/1
1/225
0/225
1/225
0/225
  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?

複雜度分析

s
t
1
2
0/1
1/225
0/225
1/225
0/225
  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?

複雜度分析

  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?
s
t
1
2
0/1
1/225
0/225
1/225
0/225

複雜度分析

  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?
s
t
1
2
1/1
1/225
1/225
1/225
1/225

複雜度分析

  • 最大流是多少
    • MFMC,最大流為 450
    • 怎麼跑?
s
t
1
2
0/1
1/225
1/225
1/225
1/225

複雜度分析

  • 在最糟情況下,\(|f|\) 每增加 1 就要找一次擴增路徑
  • 怎麼找擴增路徑?
    • 從起點 DFS
s
t
1
2
0/1
1/225
1/225
1/225
1/225

複雜度分析

  • 複雜度為 \(\mathcal O((|V| + |E|) \times \max |f|)\)
  • \(G\) 連通 \(\rightarrow |V| \in \mathcal O(|E|)\) 
  • \(\mathcal O((|V| + |E|) \times \max |f|) \subset \mathcal O(|E| \max |f|)\)
s
t
1
2
0/1
1/225
1/225
1/225
1/225

複雜度分析

  • 看起來不太糟?
    • 複雜度是偽多項式時間(pseudo polynomial),超爛
  • 那怎麼辦?
    • 哭阿
s
t
1
2
0/1
1/225
1/225
1/225
1/225

複雜度分析

  • 其實那個複雜度也不是真的
    • 如果容量是無理數怎麼辦
    • 我沒有要畫,自己讀
s
t
1
2
0/1
1/225
1/225
1/225
1/225

Ford-Fulkerson Code

  • 記得把相反邊的權重 += \(\min w(u, v) \in \mathcal P\)
typedef long long ll;

struct FordFulkerson {
    int s, t, V;
    vector< vector<int> > graph;
    vector< vector<ll> > cap;
    vector<bool> visited;

    FordFulkerson(int V): V(V), graph(V), visited(V) {
        cap.resize(V, vector<ll>(V));
    }

    void addEdge(int u, int v, int w) {
        graph[u].push_back(v);
        graph[v].push_back(u);
        cap[u][v] += w;
    }

    ll dfs(int u, ll f = INT64_MAX) {
        if (u == t) return f;
        visited[u] = 1;

        for (auto& v: graph[u]) {
            if (cap[u][v] == 0 || visited[v]) continue;

            ll fv = dfs(v, min(f, cap[u][v]));
            if (fv) {
                cap[u][v] -= fv;
                cap[v][u] += fv;
                return fv;
            }
        }

        return 0;
    }

    ll maximumFlow(int s, int t) {
        this->s = s, this->t = t;
        ll f = 0;

        for (ll augFlow; augFlow = dfs(s); f += augFlow) {
            visited.assign(V, 0);
        }

        return f;
    }
};

小插曲

  • 爛測資模擬器

可以不要偽多項式嗎

強多項式時間的作法

  • 偽多項式時間聽起來超級恐怖的爛
    • 可是你剛才不是過了嗎
    • 閉嘴
  • 為了讓執行時間和邊權與答案沒有關係,我們要採取新的策略
  • 找擴增路徑時,找路徑長最短的擴增路徑
    • 經過的點最少的
    • 用這條路徑增廣
    • 做完了

複雜度分析

  • 每次找擴增路徑做一次 BFS,複雜度 \(\mathcal O(|V| + |E|)\)
  • 每個邊最多被填滿 \(\mathcal O(|V|)\) 次
    • 擴增完要經過剛被填滿的邊或它的反向邊的距離會單調增加
    • 一條簡單路徑長度最多為 \(|V| - 1\)
  • 圖上總共有 \(|E|\) 條邊
  • 複雜度 \(\mathcal O(|E| \times |V| \times |E|) = \mathcal O(|V||E|^2)\)

Edmonds-Karp Code

  • 鄰接矩陣存容量,鄰接串列存鄰邊
typedef long long ll;

struct EdmondsKarp {
    int V;
    vector< vector<ll> > cap;
    vector< vector<int> > graph;
    vector<int> p;

    EdmondsKarp(int V): V(V), graph(V), p(V) {
        cap.resize(V, vector<ll>(V));
    }

    void addEdge(int u, int v, int w) {
        graph[u].push_back(v);
        graph[v].push_back(u);
        cap[u][v] += w;
    }

    bool bfs(int s, int t) {
        p.assign(V, 0);
        queue<int> q;
        q.push(s);

        while (q.size()) {
            auto u = q.front();
            q.pop();

            for (auto& v: graph[u]) {
                if (p[v] || !cap[u][v]) continue;

                p[v] = u;
                if (v == t) return 1;
                q.push(v);
            }
        }

        return 0;
    }

    ll maximumFlow(int s, int t) {
        ll f = 0;

        while (bfs(s, t)) {
            ll augFlow = INT64_MAX;
            for (int u = t; u != s; u = p[u]) {
                augFlow = min(augFlow, cap[p[u]][u]);
            }

            for (int u = t; u != s; u = p[u]) {
                cap[p[u]][u] -= augFlow;
                cap[u][p[u]] += augFlow;
            }

            f += augFlow;
        }

        return f;
    }
};
Dinitz

那個名字被念錯的男人

這個人有多可憐

  • Yefim A. Dinitz.
  • 這個人發明了最大流問題最常被使用的演算法
  • 然後結果推廣他的演算法的人把他的名字念錯了
  • 然後其他人都把演算法的名字記成錯的名字
  • 他甚至沒有自己的維基百科頁面
  • 只有錯的名字的演算法頁面

所以演算法是什麼

  • 剛剛一次找到一條最短路徑
  • 現在我們把當前可以走的所有最短路徑都走完
  • 做完了

作法與複雜度

  • 先 BFS 一次,將所有點和原點距離標示出來
  • 一直 DFS 找上面的路徑並增廣
  • 複雜度分析
    • 最多更新所有點距離 \(|V|-1\) 次(簡單路徑長度上限)
      • BFS \(\mathcal O(|V| + |E|)\)
      • 把當前每個路徑走完最多 \(\mathcal O(|V||E|)\)
      • 每次更新複雜度 \(\mathcal O(|V| + |E| + |V||E|) = \mathcal O(|V||E|)\) 
    • 總複雜度 \(\mathcal O(|V| \times |V||E|) = \mathcal O(|V|^2|E|)\) 
    • 夠好了!

Dinitz Code

  • 不要浪費時間做沒用的事
typedef long long ll;

struct Dinitz {
    int s, t, V;
    vector< vector<int> > graph;
    vector< vector<ll> > cap;
    vector<int> level, toCheck;

    Dinitz(int V): V(V), graph(V), level(V), toCheck(V) {
        cap.resize(V, vector<ll>(V));
    }

    void addEdge(int u, int v, int w) {
        graph[u].push_back(v);
        graph[v].push_back(u);
        cap[u][v] += w;
    }

    int bfs() {
        level.assign(V, 0);
        level[s] = 1;
        queue<int> q;
        q.push(s);

        while (q.size()) {
            int u = q.front();
            q.pop();

            for (auto& v: graph[u]) {
                if (level[v] || !cap[u][v]) continue;

                level[v] = level[u] + 1;
                q.push(v);
            }
        }

        return level[t];
    }

    ll dfs(int u, ll f = INT64_MAX) {
        if (u == t) return f;

        for (auto& i = toCheck[u]; i < graph[u].size(); i++) {
            auto& v = graph[u][i];
            if (!cap[u][v] || level[v] != level[u] + 1) continue;

            ll fv = dfs(v, min(f, cap[u][v]));
            if (fv) {
                cap[u][v] -= fv;
                cap[v][u] += fv;
                return fv;
            }
        }

        return 0;
    }

    ll maximumFlow(int s, int t) {
        this->s = s, this->t = t;
        ll f = 0, augFlow;

        while (bfs()) {
            fill(toCheck.begin(), toCheck.end(), 0);
            while (augFlow = dfs(s)) f += augFlow;
        }

        return f;
    }
};
Push Relabel

亂改複雜度就亂降,真好

大暴流

  • 你有沒有那種水開太大然後滿出來的經驗
  • 假設今天源點 \(s\) 就是那個水龍頭
    • 滿出來的水想辦法倒掉,剩下的就是最大流

演算法

  • 打開水龍頭
  • 讓水往下流
  • 上下左右擺動讓水能夠往下流
  • 沒辦法的就倒出來
  • 我需要動畫,沒有 BrineTW 的動畫我看不懂

先用鏈來看

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
0/5
3
0/6
0/4
0/7

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
0/5
3
0/6
0/4
0/7
0
0
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
0/5
3
0/6
0/4
0/7
\infty
0
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
0/5
3
0/6
0/4
0/7
\infty
0
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
0/5
3
0/6
0/4
0/7
\infty
0
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
0/6
0/4
0/7
\infty
5
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
0/6
0/4
0/7
\infty
5
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
0/6
0/4
0/7
\infty
5
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
0/6
0/4
0/7
\infty
5
0
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
0/4
0/7
\infty
0
5
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
0/4
0/7
\infty
0
5
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
0/4
0/7
\infty
0
5
0
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
4/4
0/7
\infty
0
1
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
4/4
0/7
\infty
0
1
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
4/4
0/7
\infty
0
1
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
5/6
4/4
0/7
\infty
0
1
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
s
t
1
2
5/5
3
4/6
4/4
0/7
\infty
1
0
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
5/5
3
4/6
4/4
0/7
\infty
1
0
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
5/5
3
5/6
4/4
0/7
\infty
0
1
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
5/5
3
5/6
4/4
0/7
\infty
0
1
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
4/5
3
4/6
4/4
0/7
\infty
0
0
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
4/5
3
4/6
4/4
0/7
\infty
0
0
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
4/5
3
4/6
4/4
0/7
\infty
0
0
4
0

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
4/5
3
4/6
4/4
4/7
\infty
0
0
0
4

先用鏈來看

  • 這邊如果有人看不出來最大流是多少可以先出去罰站
  • 接下來會重複來回幾次
s
t
1
2
4/5
3
4/6
4/4
4/7
\infty
0
0
0
4

更專業一點

  • 這個演算法在結束之前的流並非遵循所有流量相關定律
    • 預流(preflow)
    • 不遵守流量守恆,有些水會卡在點上,之後想辦法排掉
    • 將邊上的預流標為 \(f_p(u, v)\)
  • 定義 \(\text{excess(u)} = \displaystyle \sum_{v \in V} f_p(u, v)\)
  • 為了不要不斷重複把水倒入深處再回流,定義高度函數 \(h(u)\):
    • \(h(s) = V\)
    • \(\forall u \not = s,\ h(u) = 0\)

送水 discharge

  • 規定若要將水從 \(u\) 送到 \(v\),須滿足:
    • \(\text{excess}(u) > 0\)
    • \(\text{cap}(u, v) > 0\)
    • \(h(u) > h(v)\)
  • 看起來送不了水阿
    • 當發現水無法從 \(u\) 流出時,改變 \(u\) 的高度(relabel)
    • 改變方法:\(h'(u) = h(u) + 1\)
    • 會發現這跟直接找 \(\min h(v)\) 並令 \(h'(u) = \min h(v) + 1\) 等價
    • \(h(t)\) 永遠是 0

如何選誰開始送水

  • 大哉問
  • 不同的選法會造成不一樣的複雜度
  • 我完全不會證明你大可相信他是好的
    • 下一屆資讀講師看到了嗎,我叫你明年講證明
  • 任意選一個可以 discharge 的點:\(\mathcal O(|V|^2|E|)\)
    • 亂做就跟 Dinic 一樣快
    • 其實沒有很好寫
  • 把能送水的點放上 active 的標記
  • 怎麼選有 active 標記的點?
    • 先有 active 標記的先處理 \(\mathcal O(|V|^3)\)
    • 有 active 標記且是最高的 \(\mathcal O(|V|^2 \sqrt{|E|})\)

Push Relabel Code

  • 我改超久,哭了
typedef long long ll;

struct PushRelabel {
    int V, i;
    vector<vector<int>> graph, Q;
    vector<vector<ll>> cap;
    vector<int> h;
    vector<ll> excess;
    vector<bool> active;

    PushRelabel(int V): V(V), graph(V), Q(V << 1 ^ 1), h(V), excess(V), active(V) {
        cap.resize(V, vector<ll>(V));
    }

    void addEdge(int u, int v, int w) {
        graph[u].push_back(v);
        graph[v].push_back(u);
        cap[u][v] += w;
    }

    void enqueue(int u) {
        if (!active[u] && excess[u]) {
            active[u] = 1;
            Q[h[u]].push_back(u);
            i = max(i, h[u]);
        }
    }

    void push(int u, int v) {
        ll f = min(excess[u], cap[u][v]);
        if (h[u] > h[v] && f) {
            cap[u][v] -= f;
            cap[v][u] += f;
            excess[v] += f;
            excess[u] -= f;
            enqueue(v);
        }
    }

    void relabel(int u) {
        h[u] = V * 2 + 1;
        for (auto& v: graph[u]) if (cap[u][v]) {
            h[u] = min(h[u], h[v] + 1);
        }
        enqueue(u);
    }

    void discharge(int u) {
        for (auto& v: graph[u]) {
            if (!excess[u]) break;

            push(u, v);
        }

        if (excess[u]) relabel(u);
    }

    ll maximumFlow(int s, int t) {
        excess[s] = INT64_MAX;

        h[s] = V;
        active[s] = active[t] = 1;
        for (auto& v: graph[s]) push(s, v);

        for (i = 0; i >= 0; i--) {
            while (Q[i].size()) {
                int u = Q[i].back();
                Q[i].pop_back();
                active[u] = 0;
                discharge(u);
            }
        }

        return excess[t];
    }
};

啟發式演算法

  • 什麼是啟發式演算法
    • 很有道理的唬爛
    • 犧牲某些東西換取另外一些東西
  • 當你發現有一個點 \(u\),\(\text{excess(u)} > 0\) 且沒有其他相同高度的點
    • 水沒有辦法往下流了
    • 比 \(u\) 高的點的水再也不可能流到 \(t\)
    • 直接放棄他們,把他們的高度調到 \(|V|\)
  • 這樣可以加速演算法,但犧牲了什麼?
    • 可能沒辦法得到最大流的解

Gap Heuristic Code

  • 偷扣要先試跑再決定要不要學 source
struct PushRelabel {
    int V, i;
    vector<vector<int>> graph, Q;
    vector<vector<ll>> cap;
    vector<ll> excess;
    vector<int> h, count;
    vector<bool> active;
 
    PushRelabel(int V): V(V), graph(V), Q(V), h(V), excess(V), count(V + 1), active(V) {
        cap.resize(V, vector<ll>(V));
    }
 
    void addEdge(int u, int v, int w) {
        graph[u].push_back(v);
        graph[v].push_back(u);
        cap[u][v] += w;
    }
 
    void enqueue(int u) {
        if (!active[u] && excess[u] && h[u] < V) {
            active[u] = 1;
            Q[h[u]].push_back(u);
            i = max(i, h[u]);
        }
    }
 
    void push(int u, int v) {
        ll f = min(excess[u], cap[u][v]);
        if (h[u] == h[v] + 1 && f) {
            cap[u][v] -= f;
            cap[v][u] += f;
            excess[v] += f;
            excess[u] -= f;
            enqueue(v);
        }
    }
 
    void gap(int k) {
        for (int u = 0; u < V; u++) if (h[u] >= k) {
            count[h[u]]--;
            h[u] = max(h[u], V);
            count[h[u]]++;
            enqueue(u);
        }
    }
 
    void relabel(int u) {
        count[h[u]]--;
        h[u] = V;
        for (auto& v: graph[u]) if (cap[u][v]) {
            h[u] = min(h[u], h[v] + 1);
        }
        count[h[u]]++;
        enqueue(u);
    }
 
    void discharge(int u) {
        for (auto& v: graph[u]) {
            if (!excess[u]) break;
            
            push(u, v);
        }
 
        if (excess[u]) (count[h[u]] == 1) ? gap(h[u]) : relabel(u);
    }
 
    ll maximumFlow(int s, int t) { 
        excess[s] = INT64_MAX;
 
        count[0] = V;
        enqueue(s);
        active[t] = 1;
 
        for (i = 0; i >= 0; i--) {
            while (Q[i].size()) {
                int u = Q[i].back();
                Q[i].pop_back();
                active[u] = 0;
                discharge(u);
            }
        }

        return excess[t];
    }
};

圖論怪談

  聽說,每次有人在建國中學資訊讀書會遇到看不懂的圖論概念的時候,就會有一位不知名先生出現,在你最絕望的時候從你背後輕拍你的肩膀,當你在想「到底是誰設計出這個演算法的啦幹」的時候,他就會到你的耳邊輕輕跟你說:

 

圖論怪談

  聽說,每次有人在建國中學資訊讀書會遇到看不懂的圖論概念的時候,就會有一位不知名先生出現,在你最絕望的時候從你背後輕拍你的肩膀,當你在想「到底是誰設計出這個演算法的啦幹」的時候,他就會到你的耳邊輕輕跟你說:

 

 

「哈哈,是我啦」

圖論怪談

  聽說,每次有人在建國中學資訊讀書會遇到看不懂的圖論概念的時候,就會有一位不知名先生出現,在你最絕望的時候從你背後輕拍你的肩膀,當你在想「到底是誰設計出這個演算法的啦幹」的時候,他就會到你的耳邊輕輕跟你說:

 

 

「哈哈,是我啦」

\text{This algorithm was designed by Tarjan and Goldberg.}

例題

Solutions

例題講解

練習建模的重要性

  • 大概所有有關 flow 的題目的形狀都不會是原本的樣子
  • 練習怎麼把題目轉換成可以用 flow 演算法解決的樣子吧

二分圖最大匹配

  • CSES 1696 School Dance
  • 二分圖:給定一張圖 \(G\),能夠找到一個割 \(C(L, R)\),使得:
    • \(\forall (u, v) \in E\)
      • \(u \in L \rightarrow v \in R\)
      • \(v \in L \rightarrow u \in R\)
    • 中文:所有邊會跨過兩個點集
  • 匹配(matching):選若干條邊使得點不重複
  • 怎麼做?

二分圖

  • 看出來了嗎?

二分圖

  • 看出來了嗎?
s
t

二分圖講師表示不服

  • 二分圖匹配用二分圖的擴增路徑找的時間複雜度是 \(\mathcal O(|V||E|)\)
  • 用 Dinitz 找的時間複雜度會是 \(\mathcal O(|E|\sqrt{|V|})\)
    • 若一張圖所有除了 \(s, t\) 之外的點 \(u\) 皆符合:
      • 入度或出度其中一個是一
      • 該邊權為 1
    • 則用 Dinitz 找最大流時間複雜度會是 \(\mathcal O(|E|\sqrt{|V|})\)
  • 快到不像話,你還要學其它二分圖演算法嗎
  • 二分圖的課會介紹其他可以解其它東西的演算法,要來欸

出題者不講武德

  • 模板爛了?
    • 沒有
  • 有重邊
    • 題目沒說沒有就可以有
    • 智慧鐵人欸

小知識:我是笨

MLE?

  • 前面那個是我寫的比較好讀一點點的模板
    • 我覺得記錄回邊很煩而且不好讀
    • 所以我用了
      • 一個 \(\mathcal O(|V| + |E|)\) 的陣列記錄邊
      • 一個 \(\mathcal O(|V|^2)\) 的陣列記錄容量
    • 第三子題的測試資料警力配置圖為連結圖且不存在環路。
      圖形為連結圖代表此圖的任意兩個節點皆存在一條路徑;
      而環路表示起點和終點為同一節點的路徑。
      \(1 \le p \le 100000, 1 \le q \le 100000, 1 \le k \le 210000\)
      全部解出可獲 27 分。
    • \(|V|^2 \le (p + q)^2 = 40000000000\)

Ver. Adjacency List

  • 常數跟空間一定比較小,但我就不喜歡這樣寫
struct Dinitz {
    struct Edge {
        int v, c, r;
        Edge(int v, int c, int r): v(v), c(c), r(r) {}
    };

    int s, t, V;
    vector< vector<Edge> > graph;
    vector<int> level, toCheck;

    Dinitz(int V): V(V), graph(V), level(V), toCheck(V) {}

    void addEdge(int u, int v, int w) {
        graph[u].push_back({v, w, graph[v].size()});
        graph[v].push_back({u, 0, graph[u].size() - 1});
    }

    int bfs() {
        level.assign(V, 0);
        level[s] = 1;
        queue<int> q;
        q.push(s);

        while (q.size()) {
            int u = q.front();
            q.pop();

            for (auto& [v, c, r]: graph[u]) {
                if (level[v] || !c) continue;

                level[v] = level[u] + 1;
                q.push(v);
            }
        }

        return level[t];
    }

    int dfs(int u, int f = INT32_MAX) {
        if (u == t) return f;

        for (auto& i = toCheck[u]; i < graph[u].size(); i++) {
            auto& [v, c, r] = graph[u][i];
            if (!c || level[v] != level[u] + 1) continue;

            int fv = dfs(v, min(f, c));
            if (fv) {
                c -= fv;
                graph[v][r].c += fv;
                return fv;
            }
        }

        return 0;
    }

    ll maximumFlow(int s, int t) {
        this->s = s, this->t = t;
        ll f = 0, augFlow;

        while (bfs()) {
            fill(toCheck.begin(), toCheck.end(), 0);
            while (augFlow = dfs(s)) f += augFlow;
        }

        return f;
    }
};

「比較小」

  • 真好

多源點與多匯點

  • 假設今天有很多個源點或很多個匯點怎麼辦

多源點與多匯點

  • 假設今天有很多個源點或很多個匯點怎麼辦
s_1
t_1
1
2
6
4
s_2
t_2
t_3
3
5

多源點與多匯點

  • 假設今天有很多個源點或很多個匯點怎麼辦
  • 把源點流到一個共同源點,匯點也是
s_1
t_1
1
2
6
4
s_2
t_2
t_3
3
5
s
t

多源點與多匯點

  • 假設今天有很多個源點或很多個匯點怎麼辦
  • 把源點流到一個共同源點,匯點也是
  • 或是用 push relabel 直接全部設成 \(\text{excess}[s_i]=\infty, h[s_i] = |V|\)
s_1
t_1
1
2
6
4
s_2
t_2
t_3
3
5
s
t
\infty
\infty
\infty
\infty
\infty

點容量?

  • 如果今天不只邊有流量限制,點也有怎麼辦

點容量?

  • 如果今天不只邊有流量限制,點也有怎麼辦
1
2
s
t

點容量?

  • 如果今天不只邊有流量限制,點也有怎麼辦
    • 把點變成邊!
1
2
2
2
2
s
t
2
5
7

點容量?

  • 如果今天不只邊有流量限制,點也有怎麼辦
    • 把點變成邊!
s
t
2
5
7
2
2

點容量?

  • 如果今天不只邊有流量限制,點也有怎麼辦
    • 把點變成邊!
1_i
2_i
s
t
2
5
7
2
2
1_o
2_o

分成兩邊

  • 有時候,你需要把東西分成兩邊
    • 像是在比北市賽的時候
    • 選或不選
    • 紅隊或藍隊
    • 笨或不笨
    • 是不是 BrineTW 的粉絲
  • 以賺錢來說,可能會有
    • 選了 \(i\) 會賺 \(w_i\) 或賠 \(w_i\)
    • 選了 \(i\) 不選 \(j\) 要付出 \(w_{ij}\)

聽起來怎麼這麼像 DP

  • 聽起來一臉是 DP 的東西
  • 左想右想就是寫不出來轉移式
  • 這時候不妨砸砸看 flow!
    • 這跟 flow 有什麼關係
    • 關鍵在於「二分」
    • 什麼東西會把圖分成兩分?
      • 🈹

用通靈建圖

  •  要選還是不選呢?
    • 選了 \(u\) 會得到 \(2\)
    • 選了 \(u\) 得付出 \(5\)

用通靈建圖

  •  要選還是不選呢?
    • 選了 \(u\) 會得到 \(2\)
    • 選了 \(u\) 得付出 \(5\)
u
t
2
0
5

用通靈建圖

  •  要選還是不選呢?
    • 選了 \(u\) 會得到 \(2\)
    • 選了 \(u\) 得付出 \(5\)
  • 如果到最後還有剩下的水在 \(u\) 中代表 \(u\) 值得被選
    • 這看起來不是做加減法就可以得出的結論嗎?
5
u
t
2
0

用通靈建圖

  •  要選還是不選呢?
    • 選了 \(u\) 會得到 \(2\)
    • 選了 \(u\) 得付出 \(5\)
  • 如果到最後還有剩下的水在 \(u\) 中代表 \(u\) 值得被選
    • 這看起來不是做加減法就可以得出的結論嗎?
5
u
t
2
0

用通靈建圖

  •  要選還是不選呢?
    • 選了 \(u\) 會得到 \(2\)
    • 選了 \(u\) 得付出 \(5\)
  • 如果到最後還有剩下的水在 \(u\) 中代表 \(u\) 值得被選
    • 這看起來不是做加減法就可以得出的結論嗎?
5
u
t
2
0
v
2
4

用通靈建圖

  • 那如果在中間加上邊呢?
    • 如果選了 \(v\),就得付出 \(2\) 在 \(u\) 上
    • 選 \(v\) 不選 \(u\) 要 \(c(v, u)\)
5
u
t
2
0
v
2
4
2

用通靈建圖

  • 多源點不如換成單源點?
5
u
t
2
0
v
2
4
2
s

用通靈建圖

  • 多源點不如換成單源點?
    • 如此一來就可以表示:
      • 選了 \(u\) 會得到 \(w\):\(c(s, u) = w\)
      • 選了 \(u\) 得付出 \(w\):\(c(u, t) = w\)
      • 選 \(v\) 不選 \(u\) 得要付出 \(w\):\(c(v, u) = w\)
5
u
t
2
v
2
4
2
s

想想看

  • 不選 \(u\) 會得到 \(w\)
  • 不選 \(u\) 得付出 \(w\)
  • \(u, v\) 同一邊會得到 \(w\)
  • ...
Min Cost Flow

最小費用流

有成本的流

  • 假設今天題目給定:
    • 指定流量 \(|f|\)
    • 各邊單位流量成本
  • 求在流量為 \(|f|\) 的最小成本
  • 怎麼做

暴力做

  • 一直選成本最短的路會是好的
    • 連續最短路演算法(Successive Shortest Path Algorithm)
  • 找 \(|f|\) 次最段路徑就做完了
    • 無負邊:用 Dijkstra,複雜度 \(\mathcal O(|E|\log|E||f|)\)
    • 無負環:用 Bellman-Ford,複雜度 \(\mathcal O(|V||E||f|)\)
      • 用 SPFA,複雜度還是 \(\mathcal O(|V||E||f|)\)
    • 有負環:要把負環消掉,我沒做過
    • 是不是哪裡怪怪的?
  • 如果用用 Dijkstra,複雜度 \(\mathcal O(|E|\log|E||f|)\)
  • 聽起來快要比 Dinitz 好了欸,那我是不是可以不要用 Dinitz
    • 確實

確實個頭

確實個頭

  • 需要流量為 3
  • 成本為單位流量成本
s
t
1
2
3
4
0/2
0/4
0/2
0/5
0/5
0/3
0/6
0/1
1
1
2
2
225
3
3
8

確實個頭

  • 需要流量為 3
  • 成本為單位流量成本
s
t
1
2
3
4
0/2
0/4
0/2
0/5
0/5
0/3
0/6
0/1
1
1
2
2
225
3
3
8

確實個頭

  • 需要流量為 3
  • 成本為單位流量成本
s
t
1
2
3
4
1/2
0/4
1/2
0/5
1/5
1/3
0/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 6

確實個頭

  • 需要流量為 3
  • 成本為單位流量成本
s
t
1
2
3
4
2/2
0/4
2/2
0/5
2/5
2/3
0/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 12

確實個頭

  • 需要流量為 3
  • 成本為單位流量成本
  • 等等,不對吧
s
t
1
2
3
4
2/2
0/4
2/2
0/5
2/5
2/3
0/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 12

確實個頭

  • 需要流量為 3
  • 成本為單位流量成本
  • 等等,不對吧
    • 這段成本怎麼算?
    • 回想剩餘圖的想法!
s
t
1
2
3
4
2/2
0/4
2/2
0/5
2/5
2/3
0/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 12

確實個頭

  • 將上面的流「取消」
    • 走反邊的距離為
s
t
1
2
3
4
2/2
0/4
2/2
0/5
2/5
2/3
0/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 12

確實個頭

  • 將上面的流「取消」
    • 走反邊的距離為
s
t
1
2
3
4
2/2
0/4
2/2
0/5
2/5
2/3
0/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 12

確實個頭

  • 將上面的流「取消」
    • 走反邊的距離為
s
t
1
2
3
4
2/2
1/4
1/2
1/5
2/5
2/3
1/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 25

確實個頭

  • 將上面的流「取消」
    • 走反邊的距離為
    • 沒有辦法用 Dijkstra 直接做!
    • 可以把負邊消掉,或直接用其他演算法(像是 SPFA)
s
t
1
2
3
4
2/2
1/4
1/2
1/5
2/5
2/3
1/6
0/1
1
1
2
2
225
3
3
8
\text{cost: } 25

Min Cost Flow Code

  • 複雜度 \(\mathcal O(|V||E||f|)\)
typedef long long ll;

struct SuccessiveSP {
    struct Edge {
        int v, c, w, r;
        Edge(int v, int c, int w, int r): v(v), c(c), w(w), r(r) {}
    };

    int V;
    vector< vector<Edge> > graph;
    vector<ll> d;
    vector<int> p, edgeId;
    vector<bool> visited, inQueue;


    SuccessiveSP(int V): V(V), graph(V) {}

    void addEdge(int u, int v, int c, int w) {
        graph[u].emplace_back(v, c, w, graph[v].size());
        graph[v].emplace_back(u, 0, -w, graph[u].size() - 1);
    }

    void reset() {
        d.assign(V, INT64_MAX);
        p.assign(V, 0);
        edgeId.assign(V, 0);
        visited.assign(V, 0);
        inQueue.assign(V, 0);
    }

    pair<ll, ll> SPFA(int s, int t) {
        reset();
        d[s] = 0;
        queue<int> q;
        q.push(s);

        while (q.size()) {
            int u = q.front();
            q.pop();
            inQueue[u] = 0;

            for (int i = 0; i < graph[u].size(); i++) {
                auto& [v, c, w, r] = graph[u][i];
                if (c && d[u] + w < d[v]) {
                    p[v] = u;
                    edgeId[v] = i;
                    d[v] = d[u] + w;

                    if (inQueue[v]) continue;
                    inQueue[v] = 1;
                    q.push(v);
                }
            }
        }

        if (d[t] == INT64_MAX) return {0, 0};
        int f = INT32_MAX;
        for (int u = t; u != s; u = p[u]) {
            f = min(f, graph[p[u]][edgeId[u]].c);
        }
        for (int u = t; u != s; u = p[u]) {
            graph[p[u]][edgeId[u]].c -= f;
            graph[u][graph[p[u]][edgeId[u]].r].c += f;
        }

        return {f, d[t]};
    }
};

例題

Templates

模板區

Dinitz

  • 鄰接串列
typedef long long ll;

struct Dinitz {
    struct Edge {
        int v, c, r;
        Edge(int v, int c, int r): v(v), c(c), r(r) {}
    };

    int s, t, V;
    vector< vector<Edge> > graph;
    vector<int> level, toCheck;

    Dinitz(int V): V(V), graph(V), level(V), toCheck(V) {}

    void addEdge(int u, int v, int w) {
        graph[u].push_back({v, w, graph[v].size()});
        graph[v].push_back({u, 0, graph[u].size() - 1});
    }

    int bfs() {
        level.assign(V, 0);
        level[s] = 1;
        queue<int> q;
        q.push(s);

        while (q.size()) {
            int u = q.front();
            q.pop();

            for (auto& [v, c, r]: graph[u]) {
                if (level[v] || !c) continue;

                level[v] = level[u] + 1;
                q.push(v);
            }
        }

        return level[t];
    }

    int dfs(int u, int f = INT32_MAX) {
        if (u == t) return f;

        for (auto& i = toCheck[u]; i < graph[u].size(); i++) {
            auto& [v, c, r] = graph[u][i];
            if (!c || level[v] != level[u] + 1) continue;

            int fv = dfs(v, min(f, c));
            if (fv) {
                c -= fv;
                graph[v][r].c += fv;
                return fv;
            }
        }

        return 0;
    }

    ll maximumFlow(int s, int t) {
        this->s = s, this->t = t;
        ll f = 0, augFlow;

        while (bfs()) {
            fill(toCheck.begin(), toCheck.end(), 0);
            while (augFlow = dfs(s)) f += augFlow;
        }

        return f;
    }
};

Push Relabel

  • 鄰接串列
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

struct PushRelabel {
    struct Edge {
        int v, c, r;
        Edge(int v, int c, int r): v(v), c(c), r(r) {}
    };

    int V, i;
    vector< vector<Edge> > graph;
    vector< vector<int> > Q;
    vector<int> h;
    vector<ll> excess;
    vector<bool> active;

    PushRelabel(int V): V(V), graph(V), Q(V << 1 ^ 1), h(V), excess(V), active(V) {}

    void addEdge(int u, int v, int w) {
        graph[u].push_back({v, w, graph[v].size()});
        graph[v].push_back({u, 0, graph[u].size()});
    }

    void enqueue(int u) {
        if (!active[u] && excess[u]) {
            active[u] = 1;
            Q[h[u]].push_back(u);
            i = max(i, h[u]);
        }
    }

    void push(int u, Edge& e) {
        auto& [v, c, r] = e;
        int f = min(int(excess[u]), c);
        if (h[u] > h[v] && f) {
            c -= f;
            graph[v][r].c += f;
            excess[v] += f;
            excess[u] -= f;
            enqueue(v);
        }
    }

    void relabel(int u) {
        h[u] = V * 2 + 1;
        for (auto& [v, c, r]: graph[u]) if (c) {
            h[u] = min(h[u], h[v] + 1);
        }
        enqueue(u);
    }

    void discharge(int u) {
        for (auto& e: graph[u]) {
            if (!excess[u]) break;

            push(u, e);
        }

        if (excess[u]) relabel(u);
    }

    ll maximumFlow(int s, int t) {
        excess[s] = INT64_MAX;

        h[s] = V;
        active[s] = active[t] = 1;
        for (auto& v: graph[s]) push(s, v);

        for (i = 0; i >= 0; i--) {
            while (Q[i].size()) {
                int u = Q[i].back();
                Q[i].pop_back();
                active[u] = 0;
                discharge(u);
            }
        }

        return excess[t];
    }
};

int main() {

}

Minimum Cost Flow

  • 鄰接串列
typedef long long ll;

struct SuccessiveSP {
    struct Edge {
        int v, c, w, r;
        Edge(int v, int c, int w, int r): v(v), c(c), w(w), r(r) {}
    };

    int V;
    vector< vector<Edge> > graph;
    vector<ll> d;
    vector<int> p, edgeId;
    vector<bool> visited, inQueue;


    SuccessiveSP(int V): V(V), graph(V) {}

    void addEdge(int u, int v, int c, int w) {
        graph[u].emplace_back(v, c, w, graph[v].size());
        graph[v].emplace_back(u, 0, -w, graph[u].size() - 1);
    }

    void reset() {
        d.assign(V, INT64_MAX);
        p.assign(V, 0);
        edgeId.assign(V, 0);
        visited.assign(V, 0);
        inQueue.assign(V, 0);
    }

    pair<ll, ll> SPFA(int s, int t) {
        reset();
        d[s] = 0;
        queue<int> q;
        q.push(s);

        while (q.size()) {
            int u = q.front();
            q.pop();
            inQueue[u] = 0;

            for (int i = 0; i < graph[u].size(); i++) {
                auto& [v, c, w, r] = graph[u][i];
                if (c && d[u] + w < d[v]) {
                    p[v] = u;
                    edgeId[v] = i;
                    d[v] = d[u] + w;

                    if (inQueue[v]) continue;
                    inQueue[v] = 1;
                    q.push(v);
                }
            }
        }

        if (d[t] == INT64_MAX) return {0, 0};
        int f = INT32_MAX;
        for (int u = t; u != s; u = p[u]) {
            f = min(f, graph[p[u]][edgeId[u]].c);
        }
        for (int u = t; u != s; u = p[u]) {
            graph[p[u]][edgeId[u]].c -= f;
            graph[u][graph[p[u]][edgeId[u]].r].c += f;
        }

        return {f, d[t]};
    }
};
啪!沒了

謝謝聆聽

建中資讀 5th 川流不息

By Brine

建中資讀 5th 川流不息

心流(Flow),亦譯神馳、沉浸,是 1975 年由奇克森特米哈伊·米哈伊所提出的心理學概念。其描述人類一種完全沉浸和完全投入於活動本身的心智狀態的振奮狀態。在適當的條件下,心流狀態可以變成催眠或欣喜若狂的恍惚狀態。一些科學家已將心流本身理解為一種恍惚。

  • 410