:+1: [2020 競技程設教材 HackMD Book](https://hackmd.io/@nckuacm/ryLIV6BYI) :+1:
2019 Week 13: Graph 2
=
本週主題內容較為複雜,時間會帶給各位對圖論演算法的直覺感悟
# 最大流 (Maximum flow)
:::info
給圖 $G=(V, E)$,對邊 $(u, v) \in E$ 有**剩餘容量** $R(u, v)$
求從點 $s$ 到點 $t$ 的最大流量。
:::
以下圖為例:
邊上面的數字代表**容量上限**
> 例如 $R(e, b) = 2$
```graphviz
digraph {
a -> b[label=1]
b -> t[label=2];
s -> e[label=2]
e -> f[label=1];
rankdir="LR";
{ rank="same" s->a [label=1]}
{ rank="same" e->b [label=2]}
{ rank="same" f->t [label=1]}
}
```
而 $s \rightarrow e \rightarrow b\rightarrow t$ 可得流量 $1$ 或 $2$,
且當此路徑流量為 $1$ 時,剩餘容量皆為 $1$
> 流量 $2$ 則剩餘容量皆 $0$
```graphviz
digraph {
a -> b[label=1]
b -> t[label=2, color=red];
s -> e[label=2, color=red]
e -> f[label=1];
rankdir="LR";
{ rank="same" s->a [label=1]}
{ rank="same" e->b [label=2, color=red]}
{ rank="same" f->t [label=1]}
}
```
若 $s \rightarrow e \rightarrow f\rightarrow t$
則這流量最大只能為 $1$,是由於邊**最多承載**容量為 $1$
```graphviz
digraph {
a -> b[label=1]
b -> t[label=2];
s -> e[label="1/2", color=red]
e -> f[label="1/1", color=red];
rankdir="LR";
{ rank="same" s->a [label=1]}
{ rank="same" e->b [label=2]}
{ rank="same" f->t [label="1/1", color=red]}
}
```
==這裡 $x/y$ 表示 $x$ 流量,$y$ 容量**上限**,而剩餘容量為 $y-x$==
也就是說:
$s \rightarrow e \rightarrow f\rightarrow t$ 流量 $1$
$s \rightarrow a \rightarrow b\rightarrow t$ 流量 $1$
能得流量 $2$
```graphviz
digraph {
a -> b[label="1/1", color=red]
b -> t[label="1/2", color=red];
s -> e[label="1/2", color=red]
e -> f[label="1/1", color=red];
rankdir="LR";
{ rank="same" s->a [label="1/1", color=red]}
{ rank="same" e->b [label=2]}
{ rank="same" f->t [label="1/1", color=red]}
}
```
若是
$s \rightarrow e \rightarrow f\rightarrow t$ 流量 $1$
$s \rightarrow e \rightarrow b\rightarrow t$ 流量 $1$
$s \rightarrow a \rightarrow b\rightarrow t$ 流量 $1$
能得流量 $3$,這就是**最大流**
```graphviz
digraph {
a -> b[label="1/1", color=red]
b -> t[label="2/2", color=blue];
s -> e[label="2/2", color=blue]
e -> f[label="1/1", color=red];
rankdir="LR";
{ rank="same" s->a [label="1/1", color=red]}
{ rank="same" e->b [label="1/2", color=red]}
{ rank="same" f->t [label="1/1", color=red]}
}
```
在找到最大流量之前,得先討論怎樣的圖,存在著一條可以獲得流量($>0$)的路徑
## Augmenting path
給下圖:
```graphviz
digraph {
s
t
Node[label=""]
s -> 5 -> 6[dir=none];
2 -> 3 -> 4 -> t[dir=none];
rankdir="LR";
s -> 1 -> 2 [dir=none];
{ rank="same" 2->6 [dir=none] }
6 -> 7 -> 8 -> t [dir=none];
}
```
**隨便找**到一條流 $f_1$:
```graphviz
digraph {
s
t
Node[label=""]
s -> 5 -> 6[dir=none];
2 -> 3 -> 4 -> t[dir=none];
rankdir="LR";
s -> 1 -> 2 [color=red, penwidth=4]
{ rank="same" 2->6 [color=red, penwidth=4]};
6 -> 7 -> 8 -> t [color=red, penwidth=4]
}
```
找到第二條流 $f_2$:
這是無向邊,所以它想要往上跑
如果==邊容量允許==的話這是可行的
```graphviz
digraph {
s
t
Node[label=""]
s -> 5 -> 6[color=blue, penwidth=4];
2 -> 3 -> 4 -> t[color=blue, penwidth=4];
{ rank="same" 6->2 [color=blue, penwidth=4]};
{ rank="same" 2->6 [dir=none] }
rankdir="LR";
s -> 1 -> 2 [color=red, penwidth=4]
{ rank="same" 2->6 [color=red, penwidth=4]};
6 -> 7 -> 8 -> t [color=red, penwidth=4]
}
```
### 若 $f_1 = f_2$
中間兩流相遇的部份會**相消**
```graphviz
digraph {
s
t
Node[label=""]
{ rank="same" 2->6 [dir=none] }
rankdir="LR";
s -> 1 -> 2 [color=red, penwidth=4]
6 -> 7 -> 8 -> t [color=red, penwidth=4]
s -> 5 -> 6[color=blue, penwidth=4];
2 -> 3 -> 4 -> t[color=blue, penwidth=4];
}
```
### 若 $f_1 > f_2$
中間相遇部份 $f_1$ 多少會被減弱一些
> 所以將顏色改變一下,因為**流量**不見得與另外兩色相同
```graphviz
digraph {
s
t
Node[label=""]
s -> 1 -> 2 [color=red, penwidth=4]
6 -> 7 -> 8 -> t [color=red, penwidth=4]
rankdir="LR";
s -> 5 -> 6[color=blue, penwidth=4];
{ rank="same" 2->6 [color=orange, penwidth=4]};
2 -> 3 -> 4 -> t[color=blue, penwidth=4];
}
```
### 具體來說
給下圖:
> 幾乎跟上面例子一樣
特別注意,$R(b, f) = 3$ 而 $R(f,b)=0$
```graphviz
digraph {
s
t
b
f
Node[label=""]
s -> e -> f[ label=1];
b -> c -> d -> t[ label=1];
rankdir="LR";
s -> a -> b [ label=3];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label=3] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label=3];
}
```
一樣的 $f_1$:
```graphviz
digraph {
s
t
b
f
Node[label=""]
t [label="t:3"];
s -> e -> f[ label=1];
b -> c -> d -> t[ label=1];
rankdir="LR";
s -> a -> b [ label="3/3", color=red];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label="3/3", color=red] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label="3/3", color=red];
}
```
接著 $f_2$:
```graphviz
digraph {
s
t
b
f
Node[label=""]
t [label="t:3"];
s -> e -> f[ label="1/1", color=blue];
b -> c -> d -> t[ label=1];
rankdir="LR";
s -> a -> b [ label="3/3", color=red];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label="3/3", color=red] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label="3/3", color=red];
}
```
但是 $f_2$ **過不去**,因為剩餘容量為 $0=R(f, b)$
若在流 $f_1$ 形成時,給予**反向邊[^4]容量**,就能使得 $f_2$ 流得過去
也就是:
接下來**只需紀錄剩餘容量**就足夠知道流量為何了
所以==現在邊上數字代表的是**剩餘容量**==
```graphviz
digraph {
s
t
b
f
Node[label=""]
t [label="t:3"];
s -> e -> f[ label=1];
b -> c -> d -> t[ label=1];
rankdir="LR";
s -> a -> b [ label="0", color=red];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label="0", color=red] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label="0", color=red];
}
```
然後**給予反向邊容量**:
>這裡避免圖過於複雜,把 $0$ 邊去掉
```graphviz
digraph {
s
t
b
f
Node[label=""]
t [label="t:3"];
s -> e -> f[ label=1];
b -> c -> d -> t[ label=1];
rankdir="LR";
s -> a -> b [ label="3", dir=back];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label="3", dir=back] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label="3", dir=back];
}
```
所以現在 $f_2$ 能流過去了!
```graphviz
digraph {
s
t
b
f
Node[label=""]
t [label="t:4"];
s -> e -> f[ label=1, color=blue];
b -> c -> d -> t[ label=1, color=blue];
rankdir="LR";
s -> a -> b [ label="3", dir=back];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label="3", dir=back, color=blue] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label="3", dir=back];
}
```
實際上, $b$ 到 $t$ 的流量原本 $3$,經過 $f_2$ 後變為 $\textbf{2}$
也就是說,若==現在邊上代表的是**流量**==:
```graphviz
digraph {
s
t
b
f
Node[label=""]
t [label="t:4"];
s -> e -> f[ label=1, color=blue];
b -> c -> d -> t[ label=1, color=blue];
rankdir="LR";
s -> a -> b [ label="3", color=red];
{ rank="same" a->e [ style=invis] }
{ rank="same" b->f [ label="2", color=orange] }
{ rank="same" c->g [ style=invis] }
{ rank="same" d->h [ style=invis] }
f -> g -> h -> t [ label="3", color=red];
}
```
所以直覺的,每次**找到 augmenting path**,讓流送到 $t$
**不斷重複**這個過程,直到找不到 augmenting path,就能求得最大流
> 這稱作 Ford-Fulkerson method
## Edmonds-Karp algorithm
如果邊是無向的,
```graphviz
digraph {
rankdir=LR
u -> v[dir=none, label=x]
}
```
則將邊變為兩條**剩餘容量相等**的**有向邊**
```graphviz
digraph {
rankdir=LR
u -> v [dir=back, label=x]
u -> v [style=invis]
u -> v [label=x]
}
```
用 **BFS** 找出從 $s$ 到 $t$ 的 augmenting path
將路徑上各邊的剩餘容量扣除**流量**,以及將各反向邊[^4]剩餘容量加上**流量**。
**重複** BFS 直到找不到 augmenting path
```cpp
int max_flow = 0;
while (1) {
memset(vis, false, sizeof(vis));
vis[s] = true;
memset(flow, 0, sizeof(flow));
flow[s] = INF;
queue<int> Q;
Q.push(s);
while (!Q.empty()) {
int u = Q.front(); Q.pop();
for (int v = s; v <= t; v++) {
if (!R[u][v] || vis[v]) continue;
vis[v] = true;
flow[v] = min(flow[u], R[u][v]); // 流只挑最小的剩餘容量
Q.push(v);
pre[v] = u; // 紀錄 augmenting path
}
}
if (flow[t] == 0) break;
max_flow += flow[t];
for (int v = t; v != s; v = pre[v]) {
int u = pre[v];
R[u][v] -= flow[t];
R[v][u] += flow[t];
}
}
```
> 感受下圖像化的流程: [WilliamFiset/ Edmonds Karp Algorithm](https://www.youtube.com/watch?v=RppuJYwlcI8)
#### 練習:
[UVa OJ 820 Internet Bandwidth](https://uva.onlinejudge.org/external/8/820.pdf)
[UVa OJ 10330 Power Transmission](https://uva.onlinejudge.org/external/103/10330.pdf)
[UVa OJ 11987 Almost Union-Find](https://uva.onlinejudge.org/external/119/11987.pdf)
[POJ 2584 T-Shirt Gumbo](http://poj.org/problem?id=2584)
[POJ 3228 Gold Transportation](http://poj.org/problem?id=3228)
<!--
考慮下圖:
```graphviz
digraph {
s
t
Node[label=""]
s -> e -> t[ label=q];
rankdir="LR";
s -> a -> t[ label="p"];
{ rank="same" e->a [label="1"] }
}
```
如果每次 augmenting path 流量都為 $1$,則時間成本為 $p + q$
故演算法複雜度 -->
# Articulation point
Articulation point 中譯 關節點 (簡稱 AP),
當連通圖上此**點**被拔除,圖會分為多塊**連通圖**
:::info
給定連通圖,找到所有 AP
:::
考慮**樹**,顯然除**葉**以外,其餘的節點都為 AP
且若**根**只有一個子節點,則不為 AP
```graphviz
graph {
3 -- 4;
3 -- 5 -- 6;
5 -- 7;
7 -- 8;
}
```
如何讓 AP 不再是 AP 呢?
例如: 將 $5$ 變非 AP,可讓 $7$ 連到 $3$,及 $6$ 連到 $4$。
```graphviz
graph {
{9 [ style=invis, width=0, label=""]}
{3 -- 9 [ style=invis]}
{3 -- 9 [ style=invis]}
3 -- 4;
{"10" [ style=invis, width=0, label=""]}
{3 -- 10 [ style=invis]}
3 -- 5 -- 6;
6 -- 4 [color=red];
{ rank="same" 4--5 [ style=invis] }
5 -- 7;
7 -- 3 [color=red];
{ rank="same" 6--7 [ style=invis] }
7 -- 8;
}
```
也就是說,若問**連通圖**中有哪些點是 AP,先遍歷出棵**樹** (例如 DFS 樹)
接著直覺的,節點的子孫**有邊**回到比此節點還高[^5] (淺)的節點,則此節點非 AP。
> 高指的是 level 數字較小,淺指的是 depth 數字較小
而這個**連向祖先的邊**,稱作 **back edge**
## Tarjan’s algorithm for APs
實作上透過下列兩個函數,以找出所有 AP
- $\text{dep}(n)$ 代表節點 $n$ 的 depth
若節點還未拜訪過 $\text{dep}(n) = -1$
- $\text{low}(n)$ 代表節點 $n$ 透過 back edge 能連向**最高**節點的 depth 值,
若無 back edge 則為 $\text{dep}(n)$
![](https://i.imgur.com/GZi6H8N.gif) [^tarjamapdemo]
Tarjan 用 DFS 遍歷出樹:
```cpp
void dfs(int p, int u, int d) { // p := previous, d := depth
dep[u] = low[u] = d;
int cnt = !!d; // (parent and children of u) in dfs tree
bool back_error = false;
for (int v: E[u]) if (v != p) {
if (dep[v] != -1) {
low[u] = min(low[u], dep[v]);
continue;
}
cnt++;
dfs(u, v, d+1);
low[u] = min(low[u], low[v]);
if (low[v] >= d) back_error = true;
}
if (cnt > 1 && back_error) AP.push_back(u);
}
```
#### 練習:
[UVa OJ 315 Network](https://uva.onlinejudge.org/external/3/315.pdf)
[ICPC World Finals 2003 Building Bridges](https://icpcarchive.ecs.baylor.edu/external/27/2721.pdf)
[CODEFORCES 194C Cutting Figure](https://codeforces.com/contest/194/problem/C)
# Strongly Connected Components (SCC)
強連通是只屬於**有向圖**的概念
根據[第十一週教材](https://hackmd.io/s/SkCtEVcq4),
- 把圖的**方向**拿掉,連通的話稱此圖**弱連通**
- 加上方向後仍然連通,則此圖**強連通**
雖然有向圖不總有強連通性,但能把圖分成幾塊**強連通分塊**:
![](https://i.imgur.com/6xhmvUF.png)[^scc]
:::info
給定圖,求圖**至少**有幾塊強連通分塊
:::
## Kosaraju's Algorithm
連通意味著任兩點 $u, v$ 之間有路
那麼把 $u\leadsto v$ 的各邊反過來,對 $v\leadsto u$ 也反過來,圖理當也連通
> $u\leadsto v$ 表示 $u$ 到 $v$ 路徑
因為 $u\leadsto v$ 及 $v\leadsto u$ 兩路合併,能得到環,**環反過來還是環**
也就是說,對於強連通圖:
```graphviz
digraph {
node[label=""]
rankdir="LR"
b -> c -> a
a -> d [dir=back]
c -> e -> d
a -> b
}
```
把邊方向反過來仍具有強連通性:
```graphviz
digraph {
node[label=""]
edge[dir=back]
rankdir="LR"
b -> c -> a
a -> d [dir=forward]
c -> e -> d
a -> b
}
```
- 對於**無相圖**,[第四週教材](https://hackmd.io/s/SJ7nxwRL4#%E7%AF%84%E4%BE%8B-UVa-OJ-572-Oil-Deposits%EF%BC%9A)提到 DFS 能判斷是否具有連通性
- 對於**有向圖** DFS 拜訪到的節點與反邊圖 DFS 拜訪的**節點相同**,則為強連通
> 反邊圖就是把圖上的邊全改為反向
```cpp
void forward(int u) {
vis[u] = true;
for (int v = 0; v < n; v++) if (E[u][v] && !vis[v]) forward(v);
st.push(u);
}
void backward(int u) {
vis[u] = true;
for (int v = 0; v < n; v++) if (E[v][u] && !vis[v]) backward(v);
}
```
> 這裡 `E` 是鄰接矩陣
stack `st` 紀錄有哪些點是在 forward 走過,但 backward 沒走過
```cpp
memset(vis, false, sizeof(vis));
for (int u = 0; u < n; u++) if (!vis[u]) forward(u);
memset(vis, false, sizeof(vis));
int cnt = 0; // 紀錄有多少強連通分塊
while (!st.empty()) {
int u = st.top(); st.pop();
if (!vis[u]) cnt++, backward(u);
}
```
基於 stack 的特性,可將 forward 先一次做完,再一次次做 backward
#### 練習:
[ICPC Regionals 2008 Taipei Road Networks](https://icpcarchive.ecs.baylor.edu/external/42/4262.pdf)
[UVa OJ 247 Calling Circles](https://uva.onlinejudge.org/external/2/247.pdf)
[UVa OJ 11838 Come and Go](https://uva.onlinejudge.org/external/118/11838.pdf)
\* [ICPC Regionals 2010 Hangzhou Traffic Real Time Query System](https://icpcarchive.ecs.baylor.edu/external/48/4839.pdf)
<!--
## Tarjan's algorithm for SCCs -->
[^4]: 當邊為點 $u$ 到點 $v$,則*反向邊*指的是 $v$ 到 $u$
[^5]: 所謂的比較高,意思是距離根比較近
[^tarjamapdemo]: [Wikipedia/ A demo of Tarjan's algorithm to find cut vertices.](https://en.wikipedia.org/wiki/Biconnected_component#/media/File:TarjanAPDemoDepth.gif)
[^scc]: [Wikipedia/ Graph with strongly connected components marked](https://en.wikipedia.org/wiki/Strongly_connected_component#/media/File:Scc.png)