# 110 選手班 - 進階圖論
###### tags: `宜中資訊` `CP`
Ccucumber12
2021.08.20
---
## Outline
- DFS Tree
- Biconnected Component
- Strongly Connected Components
- 2-SAT
- Maximum Bipartite Matching
---
## DFS Tree
----
![](https://i.imgur.com/REFQBKw.png)
----
### Undirected Graph
- Tree Edge:
- 樹邊
- Real edge on DFS tree
- unvisited
- Back Edge:
- 回邊
- From descendent to ancestor
- visited
----
### Directed Graph
- Tree Edge:
- Back Edge:
- Foward Edge:
- 前向邊
- From ancestor to descendent
- Cross Edge:
- 交錯邊
- From a family to another
----
![](https://i.imgur.com/4Mc7tY3.png)
----
### Cycle
- Undirected Graph:
- Back Edge
- Directed Graph:
- Back Edge
- Cross Edge(possibly)
----
- $d[x]$: time that $x$ enters DFS
- $f[x]$: time that $x$ exists DFS
- For Edge $(i,j)$:
- $d[i] < d[j] < f[j] < f[i]$: Tree / Foward
- $d[j] < d[i] < f[i] < f[j]$: Back
- $d[j] < f[j] < d[i] < f[i]$: Cross
----
### Simplify The Problem
consider the two/four edges' performance
---
## Biconnected Component
----
### 連通性
- 討論**無向圖**上的連通性
- 邊雙連通:在一張連通無向途中,若點$u$和點$v$無法透過刪除任何一條邊,使他們不連通,則稱$u$和$v$點雙連通
- 點雙連通:在一張連通無向途中,若點$u$和點$v$無法透過刪除任何一個點,使他們不連通,則稱$u$和$v$點雙連通
----
### 邊雙連通
- 傳遞性:若$x$和$y$邊雙連通,$y$和$z$邊雙連通,則$x$和$z$邊雙連通
- 判斷一張圖是否邊雙連通:移除每條邊$e$,檢查是否整張圖還連通。
----
### 橋
- bridge / cut edge
- 割邊
- 定義:若移除邊$e$會使得圖不連通,則稱$e$為橋
----
### Problem
- 給定一張圖$G$,請找出圖中的所有橋(Bridge)
![](https://i.imgur.com/LJSo04f.png)
----
### Tarjan
- 不少他發明的算法都以他的名字命名,以至於有時會讓人混淆幾種不同的算法。 --wiki
![](https://i.imgur.com/7bYu21v.png)
----
### DFS Tree
- Back Edge: 一定不是橋
- Tree Edge: 有可能是橋
- 刪除後要透過back edge連通
- 邊$(u,v)$往上連到祖先
- $v$有back edge
- $v$的子孫有back edge且高度只少連到$u$
----
### Low Function
- $\text{low}(v):=$對於無向圖上的節點 $v$,$\text{low}(v)$被定義為「在不透過父邊的情況下,能夠到達深度最淺的祖先的深度」
----
![](https://i.imgur.com/1I00HKQ.png)
----
| | a | b | c | d | e | f | g | h |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| dep | 0 | 1 | 2 | 3 | 4 | 2 | 3 | 2 |
| low | 0 | 0 | 1 | 1 | 2 | 2 | 3 | 0 |
----
- $\text{low}(v) = \text{dep}(v)\rightarrow v$的父邊是橋
- 無法不透過父邊到達更淺的祖先
----
### Find $\text{low}$
- DFS 時從 $v$ 透過邊 $e$ 走到 $u$:
- $u$還沒拜訪過:
- $e$是樹邊,有機會從 $u$ 或他的小孩經過回邊走到更淺的祖先
- 對 $u$ 做DFS後,將 $\text{low}(v)$ 和 $\text{low}(u)$ 取 $\min$
- $u$拜訪過:
- $e$是回邊,根據定義可以更新$\text{low}(v)$
- 將 $\text{low}(v)$ 和 $\text{dep}(u)$ 取 $\min$
----
### Time Complexity
- DFS: $\mathcal{O}(V + E)$
----
```c=
const int N = 100010 ;
vector<int> g[N];
bool vis[N], bridge[N] ;
int low[N], dep[N] ;
void dfs(int x) {
vis[x] = true ;
low[x] = dep[x] ;
for(auto i:g[x]) {
if(vis[i]) {
low[x] = min(low[x], dep[i]) ;
} else {
dep[i] = dep[x] + 1 ;
dfs(i) ;
low[x] = min(low[x], low[i]) ;
}
}
if(low[x] == dep[x]) bridge[x] = true ;
}
```
----
### 邊雙連通分量
- Bridge-connected Components
- BCC
- 定義:圖上「邊雙連通」的連通子圖,通常指極大的連通子圖
----
![](https://i.imgur.com/ktvcwuT.png)
----
- 在尋找 bridge 的過程中順便找 BCC
- 如果$u$的父邊是橋,則$u$往下形成一個BCC
- 利用 stack 紀錄
----
- 拜訪一個新的點時,將該點塞入stack
- 若發現 $u$ 父邊是橋,將 stack 裡的東西取出直到 $u$ 也被取出
- 這些被取出的點形成 BCC
----
### Time Complexity
- DFS: $\mathcal{O}(V+E)$
----
```c=
const int N = 100010 ;
vector<int> g[N];
bool vis[N], bridge[N] ;
int low[N], dep[N], bcc[N], bccID ;
stack<int> stk ;
void dfs(int x) {
vis[x] = true ;
low[x] = dep[x] ;
stk.push(x) ;
for(auto i:g[x]) {
if(vis[i]) {
low[x] = min(low[x], dep[i]) ;
} else {
dep[i] = dep[x] + 1 ;
dfs(i) ;
low[x] = min(low[x], low[i]) ;
}
}
if(low[x] == dep[x]) {
bridge[x] = true ;
++bccID ;
int k ;
do {
k = stk.top() ;
bcc[k] = bccID ;
stk.pop() ;
} while(k != x) ;
}
}
```
----
### Properties
- 邊連通分量內:
- 任兩點至少有兩條路相通,且這兩條路沒有共用邊
- Robbin定理:存在一種將邊定向的方式,使得任兩點可以互相抵達(形成SCC)
----
### 縮點
- 若將每個 BCC 視為一個點,新的圖將形成一棵樹
----
### 點雙連通
- **沒有**傳遞性:若$x$和$y$點雙連通,$y$和$z$點雙連通,無法保證$x$和$z$點雙連通
- 判斷一張圖是否點雙連通:移除每個點$v$,檢查是否整張圖還連通。
----
### 割點
- cut vertex / articulation vertex(point) / AP
- 關節點
- 定義:若圖 $G$ 在點 $v$ 移除後會變得不連通,則稱 $v$ 為割點
----
### Problem
- 給定一張圖 $G$,請找出圖中所有的割點
![](https://i.imgur.com/D1QjhwS.png)
----
### Low Function
- 觀察點 $v$ 及其小孩 $u$
- 若 $u$ 無法不透過 $v$ 到達祖先時,則 $v$ 是 AP
- $\text{low}(u) \geq \text{dep}(v)\rightarrow v$ 是AP
----
### Root
- 根節點是AP若且唯若他有兩條樹邊
- 每條樹邊下的子樹各自獨立
----
### Time Complexity
- DFS: $\mathcal{O}(V+E)$
----
### 點雙連通分量
- stack 紀錄
- 若 $v$ 是AP,則下方形成一個BCC
- 點雙連通分量有交集:
- AP 要放回 stack
- 改成紀錄邊 (沒有交集)
----
### 圓方樹
- 圓點:割點
- 方點:點雙連通分量
![](https://i.imgur.com/jHYzKGo.png)
----
### 重邊
- 只有一條邊是父邊(樹邊),剩下的視為回邊
- 額外判斷往父親走了幾次
---
## Strongly Connected Components
----
### 連通性
- 討論**有向圖**上的連通性
- 弱連通(weakly connected):將有向邊轉成無向邊後圖連通
- 強連通(strongly connected):對於任兩個點 $v$ 和 $u$,都存在一條從 $v$ 走到 $u$ 的路徑及從 $u$ 走到 $v$ 的路徑
----
### 強連通分量
- Strongly Connected Components / SCC
- 定義:圖上「強連通」的連通子圖,通常指極大的連通子圖
----
![](https://i.imgur.com/PacxwsQ.png)
----
### Tarjan?
- 討論四種DFS Tree上的邊
- 透過DFS一次解決
- 實作複雜
----
### 縮點
- SCC 縮點後將形成 DAG
![](https://i.imgur.com/hi22Mg2.png)
----
- 若以拓樸排序的反序 DFS 取下每個點,每次 DFS 恰好都會取得一個SCC
- 希望可以找到一個好的順序,類似拓樸排序的反序,得以產生同樣的效果
----
- 討論兩個點 $x$ 和 $y$
- 若 $x$ 能到達 $y$,但 $y$ 無法到達 $x$,則 $x$ 和 $y$ 在不同SCC
- 希望 $y$ 比 $x$ 早 DFS
----
### Kosaraju
- 建立反圖 $G'$,在 $G'$ 上 DFS 並記錄每個點離開的時間
- 依照離開時間大到小的順序在原圖 G 上 DFS 即可正確找出 SCC
----
### Proof
- 對於點對 $x$ 和 $y$,若 $x$ 能到達 $y$,但 $y$ 無法到達 $x$
- 考慮在反向圖 $G'$ 上 DFS 到 $y$ 時的狀況
- $x$已經拜訪過了:$x$已經離開,$x$ 時間小於 $y$
- $x$尚未拜訪過:$y$ 將會 DFS 到 $x$,且 $y$ 至少要等 $x$ 離開後才能離開,$x$ 時間小於 $y$
----
![](https://i.imgur.com/s37iuDE.png)
----
### Time Complexity
- 兩次 DFS
- $\mathcal{O}(V+E)$
----
```c=
const int N = 100010 ;
vector<int> g[N], rg[N], ord;
int scc[N], sccID ;
bool vis[N] ;
void dfsRev(int x) {
vis[x] = true ;
for(auto i:rg[x])
if(!vis[i])
dfsRev(i) ;
ord.push_back(x) ;
}
void dfs(int x) {
vis[x] = true ;
scc[x] = sccID ;
for(auto i:g[x])
if(!vis[i])
dfs(i) ;
}
void kosaraju(int n) {
for(int i=1; i<=n; ++i)
if(!vis[i])
dfsRev(i) ;
reverse(ord.begin(), ord.end()) ;
fill(vis, vis+N, 0) ;
for(auto i:ord) if(!vis[i]) {
++sccID ;
dfs(i) ;
}
}
```
---
## 2-SAT
----
### 2-Satifiability
- 有 $N$ 個布林變數(Boolean variables):$x_1,x_2,\dots,x_N$
- 運算式:$(x_1 \vee x_2) \wedge (\neg x_1 \vee x_3) \wedge (\neg x_2 \vee \neg x_5) \wedge \dots$
- 找到一組 $x_1,x_2,\dots,x_N$ 使得運算式為真
----
### 老媽與偏食小孩
- 有一個媽媽要他的小孩吃各種蔬菜,可是小孩不肯,於是他們在討價還價後定出了以下的條件
- 青椒、菠菜至少選一個
- 青椒、番茄至少選一個
- 番茄、苦瓜至少選一個
- 不吃苦瓜、不吃青椒至少選一個
- 請問要選擇那些菜色才能符合兩人開出的條件?
----
- 青椒($x_1$)、菠菜($x_2$)、番茄($x_3$)、苦瓜($x_4$)
- 青椒、菠菜至少選一個 $(x_1 \vee x_2)$
- 青椒、番茄至少選一個 $(x_1 \vee x_3)$
- 番茄、苦瓜至少選一個 $(x_3 \vee x_4)$
- 不吃苦瓜、不吃青椒至少選一個 $(\neg x_4 \vee \neg x_1)$
- $(x_1 \vee x_2) \wedge (x_1 \vee x_3) \wedge (x_3 \vee x_4) \wedge (\neg x_4 \vee \neg x_1)$
----
### 建立模型
- 每個變數只有兩種狀態 $x = True \vee False$
- 將每個變數拆成兩種個討論
- True: $x$
- False: $\neg x$
----
- $(a \vee b)$
- $\neg a \longrightarrow b$
- $\neg b \longrightarrow a$
- $(\neg a \vee b)$
- $a \longrightarrow b$
- $\neg b \longrightarrow \neg a$
----
- $N$ 個變數形成 $2N$ 個點
- $M$ 個條件形成 $2M$ 條有向邊
- 有向圖
----
### 判斷是否有解
- $a \rightarrow \neg b$:若 $a$ 為真,則 $b$ 必定為假
- $x \rightarrow \neg x$:若 $x$ 為真,則 $x$ 必定為假 $\Rightarrow$ $x$ 不能為真
- $\neg x \rightarrow x$:若 $x$ 為假,則 $x$ 必定為真 $\Rightarrow$ $x$ 不能為假
----
- $x$ 無解的條件:當 $x \rightarrow \neg x$ 和 $\neg x \rightarrow x$ 同時存在
- $\implies$ 當 $x$ 能走到 $\neg x$ 且 $\neg x$ 能走到 $x$,$x$ 無解
- $\implies$ $x$ 和 $\neg x$ 在同一個 SCC 內
----
### Solution
- 對於變數個數建點
- 對於每個條件建立有向邊
- 在圖上做 SCC
- 檢查每個變數是否矛盾
----
### Time Complexity
- $N$ 個變數,$M$ 個條件
- 建點:$\mathcal{O}(N)$
- 建邊:$\mathcal{O}(M)$
- SCC:$\mathcal{O}(N+M)$
- 檢查:$\mathcal{O}(N)$
- Total:$\mathcal{O}(N+M)$
----
### 找到一組解
- 同一個 SCC 內的點必定全選或全不選
- 在縮圖上以拓樸排序反向設定解
- $x$ 在 $\neg x$ 前面:$x \rightarrow \neg x$,$x$ 為假
- $\neg x$ 在 $x$ 前面:$\neg x \rightarrow x$,$x$ 為真
----
### XOR
- $a \oplus b$
- $(a \vee \neg b) \wedge (\neg a \vee b)$
- 四個有向邊
----
### K-SAT
- $(a \vee b \vee c \vee\dots \vee n) \wedge (\dots$
- 一個條件裡面最多有 $k$ 個元素取 OR
- ex. 1-SAT:$\neg x_1 \wedge x_2 \wedge \neg x_3 \wedge\dots \wedge x_N$
----
### 3-SAT
- 所有 NP-complete 皆能轉換為 3-SAT 問題
- 目前沒有好的解法
---
## Maximum Bipartite Matching
----
### 匹配
- Matching
- 一張無向圖中相鄰兩點配成一對,且每個點最多只能與一個人配對
- 在圖中挑出一些邊,使得這些邊各自獨立
----
![](https://i.imgur.com/l9FDEem.png)
----
### 二分圖最大匹配
- 討論一張二分圖上的匹配問題
- 通常要找到最大的匹配數量
- Ex. 交友網站上男女各自找有興趣的異性,當兩人對彼此都有興趣則形成一條邊,請找出可能的最大成功配對數量。
----
![](https://i.imgur.com/U3KLNdf.png)
----
### 網路流
- 源點接上左邊所有點,且容量為 $1$
- 匯點接上右邊所有點,且容量為 $1$
- 最大匹配 = 最大流
- Dinic's 算法:$\mathcal{O}(\sqrt V E)$
----
### 交錯路徑
- alternating path
- 匹配邊與未匹配邊彼此相間的路徑
- $(x_1,[y_1,x_2],[y_2,x_3],\dots,[y_{n-1},x_n],[y_n,x_{n+1}])$
- 只有首點或末點未匹配
- 顛倒交錯路徑上的匹配邊與未匹配邊不影響匹配數量
----
### 增廣路徑
- augmenting path
- 首點和末點皆未匹配
- $(x_1,[y_1,x_2],[y_2,x_3],\dots,[y_{n-1},x_n],y_n)$
- 顛倒增廣路徑上的匹配邊與未匹配邊會使匹配數量$+1$
----
### Berge's Lemma
- 一組匹配 $M$ 為圖 $G$ 中的最大匹配若且唯若圖上不存在對於 $M$ 來說的增廣路徑
- 在圖上不斷的找增廣路徑,如果成功找到就翻轉他,直到找不到為止
----
### Augmenting Path Algorithm
- 二分圖中左邊的點集 $X$,右邊的點集 $X$
- 對於每個左邊的點 $x \in X$:
- 從 $x$ 開始 DFS,若鄰居 $y$ 未被匹配,則找到了路竟$(x,y)$,將 $y$ 與 $x$ 匹配
- 若鄰居 $y$ 已經匹配,考慮他的匹配點 $z\ (z \in X)$,如果 $z$ 有增廣路徑的話,把 $(x,y)$ 接過去還是一條增廣路徑,遞迴 $z$。
----
### Time Complexity
- 對於左邊每個點,最多會 DFS 整張圖一次
- $\mathcal{O}(V) \times \mathcal{O}(E) = \mathcal{O}(VE)$
----
```c=
const int N = 1010 ;
vector<int> g[N];
int py[N] ;
bool vis[N] ;
bool dfs(int x) {
for(auto i:g[x]) if(!vis[i]) {
vis[i] = true ;
if(!py[i] || dfs(py[i])) {
py[i] = x ;
return true ;
}
}
return false ;
}
int APA(int n) {
int ret = 0 ;
for(int i=1; i<=n; ++i) {
fill(vis, vis+N, 0) ;
if(dfs(i)) ++ret ;
}
}
```
----
### 二分圖最大權匹配
- 匈牙利算法(KM算法)
- 本質上也是利用 增廣路徑算法
- 在點上加權重 $l[x], l[y]$ 使得 $l[x] + l[y] \geq w(x,y)$
- 逐漸降低權重使得 $l[x] + l[y] = w(x,y)$
- 一般:$\mathcal{O}(V^2E)$、優化:$\mathcal{O}(VE)$
- Tutorial: https://blog.csdn.net/sixdaycoder/article/details/47720471
----
一般
```c=
struct KM {
int n ;
vec<vec<pii>> g ;
vec<int> lx, ly, vx, vy, p;
KM (int _n) {
n = _n ;
g.rs(n + 1) ; p.rs(n + 1) ;
lx.rs(n + 1) ; ly.rs(n + 1) ;
vx.rs(n + 1) ; vy.rs(n + 1) ;
}
void addEdge(int a, int b, int c) {
g[a].EB(b, c);
}
bool dfs(int x) {
vx[x] = true;
for(auto [i, u]:g[x]) if(!vy[i] && lx[x] + ly[i] == u) {
vy[i] = true;
if(!p[i] || dfs(p[i])) {
p[i] = x ;
return true;
}
}
return false ;
}
int exe () {
rep1(i, n) for(auto j:g[i])
amax(lx[i], j.S);
rep1(i, n) {
while(true) {
fill(ALL(vx), 0);
fill(ALL(vy), 0);
if(dfs(i)) break;
int d = INF;
rep1(i, n) if(vx[i])
for(auto [j, u]:g[i]) if(!vy[j])
amin(d, lx[i] + ly[j] - u) ;
rep1(i, n) {
if(vx[i]) lx[i] -= d ;
if(vy[i]) ly[i] += d ;
}
}
}
int ret = 0;
rep1(i, n) ret += lx[i] + ly[i] ;
return ret ;
}
};
```
----
優化
```c=
struct KM {
int n;
vec<vec<int>> g;
vec<int> lx, ly, slk, vis, pre, mat;
KM (int _n) {
n = _n ;
g.assign(n+1, vec<int>(n+1, -INF));
lx.rs(n+1); ly.rs(n+1);
vis.rs(n+1); mat.rs(n+1);
slk.rs(n+1); pre.rs(n+1);
}
void addEdge(int a, int b, int c) {
g[a][b] = c;
}
void bfs(int x) {
int px, py = 0, ny = 0, d;
rep(i, n+1) vis[i] = pre[i] = 0, slk[i] = INF;
mat[py] = x;
do {
px = mat[py], vis[py] = true, d = INF;
rep1(i, n) if(!vis[i]) {
if(slk[i] > lx[px] + ly[i] - g[px][i])
slk[i] = lx[px] + ly[i] - g[px][i], pre[i] = py;
if(d > slk[i])
d = slk[i], ny = i;
}
rep(i, n+1)
if(vis[i]) lx[mat[i]] -= d, ly[i] += d;
else slk[i] -= d;
py = ny;
} while(mat[py]);
while(py) mat[py] = mat[pre[py]], py = pre[py];
}
ll match() {
rep1(i, n) bfs(i);
ll ret = 0;
rep1(i, n) ret += lx[i] + ly[i];
return ret;
}
};
```
----
### 一般圖最大匹配
- 增廣路徑可能形成環,稱為**花**(blossom)
- 進行**縮花**(contract)的動作
- 我也不會QWQ
---
## Credit
- Wikipedia
- OI Wiki
- 演算法筆記
- IONC - 日月掛長
- https://mathworld.wolfram.com/PerfectMatching.html
- https://medium.com/swlh/on-the-importance-of-knowing-names-and-maximum-cardinality-matching-in-bipartite-graphs-180ef7f48b7e
{"metaMigratedAt":"2023-06-16T08:04:29.013Z","metaMigratedFrom":"Content","title":"110 選手班 - 進階圖論","breaks":true,"contributors":"[{\"id\":\"45262441-89e4-46ca-959b-c0d6fadb64e2\",\"add\":13185,\"del\":360}]"}