yungyao
用8wcp認識圖論
IOI 2022 Gold Medalist C. Liu (2021)
把dfs過程記錄起來
把邊分成在DFS Tree上的Tree Edge以及不在的Back Edge
對於在DFS時訪問到新節點的邊設為Tree Edge
往已經訪問過的節點則設為Back Edge
拔掉一個人的關節,他就會死
關節點!
拔掉該點後,圖會變得不連通
在DFS Tree上做事!
維護兩個個函式 \(low\) 及 \(dep\)
\(low(x)\) 表示由 \(x\) 開始往子孫走
再經由至多一條Back Edge能走到的最淺深度
\(dep\) 則代表在DFS Tree上的深度
1
9
8
7
6
5
4
3
2
\(dep = 1\)
\(dep = 2\)
\(dep = 3\)
\(dep = 4\)
\(low(2) = 1\)
\(low(3) = 1\)
\(low(4) = 4\)
\(low(1) = 1\)
\(low(5) = 1\)
\(low(6) = 1\)
\(low(7) = 1\)
\(low(8) = 2\)
\(low(9) = 2\)
在DFS Tree上的節點 \(u\)
就是關節點
利用樹DP
vector <int> adj[maxn];
int dep[maxn], low[maxn];
bool vis[maxn];
vector <int> ap_list;
void dfs(int x, int fr = 0){ //int fr = 0 表示在沒有傳入值下預設為 0
low[x] = dep[x] = dep[fr] + 1; //初始化數值
vis[x] = true;
int c = 0;
bool flag = true;
for (auto i:adj[x]) if (i != fr){
if (vis[i]) low[x] = min(low[x], dep[i]); //遇到Back Edge,更新low
else{
++c;
dfs(i, x); //遇到Tree Edge,向下dfs
low[x] = min(low[x], low[i]); //更新low
if (fr and low[i] >= dep[x] and flag){ //遇到子節點low值大於等於x的深度
flag = false;//注意不要重複設置關節點
ap_list.pb(x);
}
}
}
if (!fr and c > 1) ap_list.pb(x); //特判根節點的case
}
跟關節點差不多
橋!
拔掉該邊後,圖會變得不連通
在DFS Tree上做事!
維護兩個個函式 \(low\) 及 \(dep\)
\(low(x)\) 表示由 \(x\) 開始往子孫走
再經由至多一條Back Edge能走到的最淺深度
\(dep\) 則代表在DFS Tree上的深度
跟關節點一樣!
邊雙連通:任兩點間都存在至少兩條簡單路徑
邊雙連通分量,意指把圖分成好幾個邊雙連通的分量
用一個stack紀錄dfs序
遇到橋時,把所有 stack 內 \(x\) 以上的點放入目前的分量
\(x\) 的子樹內的點不屬於其他分量的點,都屬於這個新的分量
vector <int> adj[maxn];
int bcccnt = 0;
int bl[maxn], dep[maxn], low[maxn];
bool vis[maxn];
stack <int, vector<int>> dfsr;
void dfs(int x, int fr){
dfsr.push(x);
for (auto &i:adj[x]) if (i != fr){
if (vis[i]) low[x] = min(low[x], dep[i]);
else{
dfs(i, x);
low[x] = min(low[x], low[i]);
}
}
if (low[x] == dep[x]){
++bcccnt;
int k;
do{
k = dfsr.top(); dfsr.pop();
bl[k] = bcccnt;
}while (k != x);
}
}
可以觀察到,把連通分量縮成一個點之後
剩下來的邊都是橋
\(\Rightarrow\) 會形成一棵樹
於是我們就可以把一般圖上的問題轉到樹上了
Credit:奇怪的中國網站
套到關節點上會形成點雙連通分量和圓方樹
但她好難,我們不要理他
我去年講過ㄌ,好欸少做一份簡報
強強的連通分量
強連通表示對於所有節點對 \((a, b)\)
都同時存在 \(a\) 到 \(b\) 和 \(b\) 到 \(a\) 的路徑
在dfs的過程中,紀錄進入/離開節點的順序
複雜度都是線性 \(O(n+m)\)
在原圖外,額外存一張由反向的邊組成的反圖
我們可以由定義知道若在正圖和反圖上都可以從 \(a\) 走到 \(b\)
則 \(a, b\) 屬於同一SCC
我們也可以觀察到,stack 頂端的點屬於的SCC入度必為0
也就是在反圖上的出度為0
因此每次反圖上所走到的節點必屬於同一SCC
而把這些點拔除後剩下的圖會有一樣的性質
我們就可以用數歸證明Kosaraju的正確性了
\(Q. E. D.\)
繼續做 \(low\) 函式
但存的是 DFS 進入序
且我們只用目前還在 stack 內的點去更新 \(low\)
如果遇到一個點 \(x \text{ s.t. } low(x) = time\_stamp(x)\)
則我們將 stack 中 \(x\) 以上的點都丟入一個新的SCC
The proof is left as an exercise for the reader.(X
邊雙連通分量把圖變成樹
那SCC呢?
這裡有一張圖,著相同顏色的屬於同一SCC
縮成一張DAG了!
Credit: FHVirus's Slides
(這題也有不用SCC的解)
學會2-SAT了,來試試看3-SAT怎麼做吧
資芽仔應該都聽過ㄌ,幫我講(X
我們定義 Clause 為一個或多個布林變數取正或取反的 OR 和
而若將多個 Clause 取AND
並且詢問是否存在一組解使整個表達式為 TRUE
即為一個 SAT-problem
(a or b or c) and (!a or !d or e) and (d or !e)
一個SAT問題
2-SAT為一SAT問題的特例
也就是每個 Clause 只會由恰兩個變數組成
(a or b) and (!b or c) and (b or d) and (!c or !d)
一個2-SAT問題
我們可以對於每個布林變數維護兩個節點
分別代表其取True或False
而對於一個2-Clause,我們可以用兩條邊
分別表示一個值取特定值後另一變數必須取特定值
如果存在一個點取正取反都在同一SCC內則無解
否則按照反圖上的拓樸排序取值!
The proof is left as an exercise for readers(X