Graph
By LFsWang
DFS Tree
DFS Tree
- 由一個點做DFS之後構成的樹
- 根據邊的在遍歷的過程中連接狀態有不同性質
DFS Tree
DFS Tree
定義\(R(v)\)表示點\(v\)在DFS Tree的父節點
而\(R(root)\)是空節點
Connected Component
連通分量
連通分量
設集合\(C\)為無向圖\(G\)的一個連通分量
若點\(x,y\in G\)
\(x,y \in C\Leftrightarrow x,y\)之間有一條路徑
連通分量
鄰點:若存在\(e(v,w)\),則v,w互為鄰點
因此\(v\)的所有鄰點\(N(v)\)是\(v\)的邊可以到達的所有點集合。
連通分量
路徑:若\(a,b\)在同一個連通塊,\(path(a,b)\)表示a到b的任意一種路徑。
連通分量
- 每一個點都位在一個連通分量
- 若有路徑\(path(x,y)\),就有\(path(y,x)\)
- 若存在\(path(x,y),path(y,z)\),就存在\(path(x,z)\)
看起來就像disjoint set
連通分量測試
定義函數\(C(g)\)表示圖\(g\)的連通分量數量,給定一張無向圖\(G\)
- 求\(C(G)=?\)
DFS
根據連通分量的特性,DFS任意一個點會走遍該點的連通分量
因此就看用了幾次DFS就能知道答案
AP & Bridge
關節點與橋
Articulation Points
有一張無向連通圖\(G\)
若點\(v\)是關節點(AP)\(\Leftrightarrow\) \(G\)刪除\(v\)後,\(C(G)>1\)
Bridge
有一張無向連通圖
若邊\(e\)是橋\(\Leftrightarrow\) 從\(G\)刪除\(e\)後,\(C(G)>1\)
關節點與橋
給一張無向圖,請找出圖裡面所有的關節點 (與橋)。
DFS 爆搜
枚舉所有的點當關節點,模擬刪掉後,是否依然能遍歷所有的節點
橋亦同
DFS:\(O(E+V)\)
總複雜度:\(O((E+V)^2)\)
Tarjan's algo for AP
圖論大師Tarjan在1973年提出了在\(O(N)\)時間找出AP的演算法
實際上同方法也可以用來找Bridge
note. O(N)在圖論表示O(E+V)
Tarjan's algo for AP
定義\(T\)是\(G\)的DFS搜索樹
對於點\(v \in G\),定義函數如下
\(D(v)\):表示點\(v\)在\(T\)的深度
$$D=1$$
$$D=2$$
$$D=3$$
$$D=4$$
Tarjan's algo for AP
定義\(T\)是\(G\)的DFS搜索樹
對於點\(v \in G\),定義函數如下
\(L(v)\):\(v\)、\(N(v)-R(v)\)
、\(v\)的子樹及子樹在G中所有鄰點
這團東西所有D(x)的最小值
Low point
✔
✔
✔
自己
子樹
子樹臨點
L(x)就是打勾部分D(x)的最小值
✔
臨點
Low point
a是v的臨點,不包含parent
b是v子樹的根結點
$$L(v) = min( D(v),D(a),L(b) )$$
Tarjan's algo for AP
定理1. Root r是AP\(\Leftrightarrow deg(r) > 1\)
Proof
若移除\(r\)會使原圖產生\(\deg(r)\)個連通分量
根據AP的定義得證
Tarjan's algo for AP
定理2. \(v\)是非root的點,w是\(v\)的鄰點。
若\(v\)是AP\(\Leftrightarrow \exists w :L(w)\geq D(v)\)
\(v\)
\(w\)
\(L(w)\)
因為w無法走到比v還要前面的點
移除v必然會至少分成w與r的兩個連通分量
\(r\)
Tarjan's algo for Bridge
定理3. \(v\)是非root的點,\(w\)是\(v\)的鄰點。
若\(e(v,w)\)是Bridge\(\Leftrightarrow \exists w :L(w) > D(v)\)
\(v\)
\(w\)
\(L(w)\)
因為w無法走到比w還要前面的點
移除e必然會至少分成w與r的兩個連通分量
\(r\)
\(e(v,w)\)
Tarjan's algo for AP
在Tarjan的原始論文中,D(x)是定義為DFS遍歷順序的時間戳,也是較為常見的實作方法
Tarjan's algo for AP
void DFS(int v, int fa) //call DFS(v,v) at first
{
D[v] = L[v] = timestamp++; //timestamp > 0
int childCount = 0; //定理1 for root
bool isAP = false;
for (int w:adj[v])
{
if( w==fa ) continue;
if ( !D[w] ) // D[w] = 0 if not visited
{
DFS(w,v);
childCount++;
if (D[v]<=L[w]) isAP = true; //定理2
if (D[v]< L[w]) edgeBridge.emplace_back(v, w);//定理3
L[v] = min(L[v], L[w]);
}
L[v] = min(L[v], D[w]);
}
if ( v == fa && childCount < 2 ) isAP = false; //定理1, v==fa只是確認root
if ( isAP ) nodeAP.push_back(v);
return ;
}
練習題
給圖找Cutting Point
UVa 315
- CP aka AP
Strongly Connected Component
強連通分量
SCC定義
令G是有向圖,\(v,w\)是G上的兩點,\(S\)是G的一個SCC,
$$v,w \in S \Leftrightarrow \exists path(v,w),path(w,v)$$
SCC由數個有向環構成!
SCC與縮點
縮點操作
將同一個強連通分量內的所有節點合併為一個點,構成一張新圖。
SCC與縮點
定理4.設\(G\prime\)是\(G\)經由縮點操作生成的圖
\(G\prime\)是有向無環圖(DAG)
SCC與縮點
Proof.
若\(G\prime\)有環,那這一個環構成一個SCC,根據縮點操作,這個環必須合併成一個點,故\(G\prime\)不存在環
SCC
有兩個線性方法可以求SCC
- Tarjan's SCC Algorithm
- Kosaraju's Algorithm
Tarjan's SCC Algorithm
圖論大師Tarjan在連通分量有許多著作,這也是其中一個
方法沿用了AP/Bridge的\(L(x),D(x)\)函數
Tarjan's SCC Algorithm
定理5. 若點\(v\)有\(L(v)=D(v)\),\(v\)子樹所有未縮點的點構成SCC
Tarjan's SCC Algorithm
void DFS(int v, int fa) { //call DFS(v,v) at first
D[v] = L[v] = timestamp++; //timestamp > 0
st.push(v);
for (int w:adj[v]) {
if( w==fa ) continue;
if ( !D[w] ) { // D[w] = 0 if not visited
DFS(w,v);
L[v] = min(L[v], L[w]);
}
L[v] = min(L[v], D[w]);
}
if( D[v] == L[v] ) {
int x;
do{
x = st.top();
st.pop();
scc[x] = SCCID;
}while( x != v );
SCCID++;
}
}
Kosaraju's Algorithm
兩次DFS的方法
用後序走訪走過整張圖放入stack
由stack一個一個拿出來在反圖DFS,走到的都是該點的SCC
Kosaraju's Algorithm
個人獨門方法 比某模板短一半的code
void DFS(vector<int> *dG, int v, int k=-1){
visited[v] = true;
contract[v] = k;
for (int w:dG[v])
if (!visited[w])
DFS(dG,w,k);
if(dG==G) st.push(v);
}
int Kosaraju(int N){
memset(visited,0,sizeof(visited));
for(int i=0; i<N; ++i) if(!visited[i]) DFS(G,i);
memset(visited,0,sizeof(visited));
while(!st.empty()){
if (!visited[st.top()]) DFS(GT, st.top(), sccID++);
st.pop();
}
return sccID;
}
練習題
給定一些骨牌,且已知推倒每張骨牌會連動哪一些骨牌,問至少需推倒幾張骨牌才能使所有骨牌都倒下。
UVa 11504
2-SAT
滿足性問題
SAT問題
以AND運算將許多OR運算式合併成的算式
$$(a \lor b \lor c )\land (c \lor \neg b \lor d )\land (e)=1?$$
如果在最多OR的子運算式中,最多只有K個變數,那麼我們稱這一個問題為K-SAT問題。
SAT問題
定理7. 所有SAT問題都能轉換成3-SAT問題
定理6. 所有NPC問題都能轉換成SAT問題
定理8. 3-SAT問題是NPC問題
2-SAT
如果3-SAT問題可以被進一步化簡為2-SAT問題
定理9. 2-SAT問題是P問題
Krom's Algorithm
可以在\(O(N)\)的時間判定2-SAT問題
$$(a \lor b)\land (c \lor \neg b )\land (e)=1?$$
運用SSC的概念
依賴關係
考慮布林式\(a \lor b\),若要讓此式子為true
若a選擇false,b一定要選true
若b選擇false,a一定要選true
依賴關係
將2SAT依賴關係表示成一張圖\(G_r\)
有向邊(A,B)表示若選擇A,就要選擇B
$$B\prime$$
$$A\prime$$
$$A$$
$$B$$
\(X\prime\)表示X使用false的狀態
依賴關係
怎麼連 ?
( not a or b )
( a )
( not a )
( a xor b )
Krom's Algorithm
定理10. 若2SAT問題無解,則存在X使
\(X,X\prime\)位在同一個SCC
反之,可以構造一組可行的解
X = true
X = false
Krom's Algorithm
一種構造方法
var[i] = scc[i]>scc[not(i)];
//Kosaraju作的DAG,SCC深度深的ID比較大
沿著縮點完的DAG回朔依賴關係即可獲的解答
練習題
TNFSH ZJ B017
Lowest Common Ancestor
最近共同祖先
定根
給一個隨便的無根樹很麻煩,所以我們隨便挑一個點當作根
LCA
定義:點(x,y)往root的最短路徑上,第一個共有的點
LCA(x,y)
x
y
樹上兩點間詢問
如果詢問的答案可以透過扣除來調整,可以考慮LCA
ex 兩點間最短路徑長
$$L(x,y) = L(R,x)+L(R,y)-2\times L(R,lca(x,y))$$
倍增法
求一個點的\(2^k\)祖先是哪一點,使用DP來完成
定義dp[i][j]表示點i的第\(2^j\)個祖先節點是誰
$$dp[i][j]=\begin{cases}-1 & \text{not exist} \\ fa(i)&j=0\\dp[dp[i][j-1]][j-1]&dp[i][j-1]\neq -1\end{cases}$$
LCA DP
for (int i=0; i<lgN; ++i)
for (int x=0; x<n; ++x)
{
if (P[x][i]==-1) P[x][i+1] = -1;
else P[x][i+1] = P[P[x][i]][i];
}
先用dfs求dp[i][0]的答案
Jump
使用DP表格,可以在\(O(logN)\)的時間求出\(v\)的任意遠祖先
把移動距離二進位分解
再移動即可
int jump(int x,int d)
{
for(int i=0;i<lgN;++i)
if( (d>>i)&1 )
x=dp[x][i];
return x;
}
LCA
使用DP表格+Jump求LCA
int find_lca(int a,int b){
if(dep[a]>dep[b])
swap(a,b);
b=jump(b,dep[b]-dep[a]);
if(a==b)
return a;
}
Step 1. 調整深度
把a,b移動到相同深度的位置去
如果移動完a=b就完成
LCA
使用DP表格+Jump求LCA
int find_lca(int a,int b){
if(dep[a]>dep[b])
swap(a,b);
b=jump(b,dep[b]-dep[a]);
if(a==b)
return a;
for(int i=MAX_LOG;i>=0;--i)
{
if(dp[a][i]!=dp[b][i])
{
a=dp[a][i];
b=dp[b][i];
}
}
}
Step 2. 二分搜
k由大到小跳\(2^k\)步
如果是同樣的點,就不動,不然就跳上去
LCA
使用DP表格+Jump求LCA
int find_lca(int a,int b){
if(dep[a]>dep[b])
swap(a,b);
b=jump(b,dep[b]-dep[a]);
if(a==b)
return a;
for(int i=MAX_LOG;i>=0;--i)
{
if(dp[a][i]!=dp[b][i])
{
a=dp[a][i];
b=dp[b][i];
}
}
return dp[a][0];
}
Step 3.完成
最後會停再LCA下方
丟回dp答案即可
樹上詢問
你得到了一棵N個節點的樹!!(頂點編號1~N)
你選定了一個起點S跟一個終點T,
現在你從S走到T,請求出走第K步時走到的點。
(K=0表示還待在S)。
TIOJ 1687
樹序列化
樹序列化
- 又稱作樹壓平
- 在進階資料結構中,有提到很多處理一維序列的方法
- 如果不知道Tree的問題怎麼解,是否有辦法把樹轉成一圍序列?
歐拉走訪
- 沿著樹的邊緣走一圈
- DFS進去與出來都看一次
歐拉走訪
- 走訪結果
\(\{1,2,5,2,6,2,7,2,1,3,1,4,1\}\)
- 重要性質
一個點進去與出來構成的區間,包含子樹的所有點
Lowest Common Ancestor
最近共同祖先
更神奇的作法
- 可以做到\(O(n)\)預處裡,\(O(1)\)查詢
Method of Four Russians
4個俄羅斯人法(!?),把\(O(\log n)\)壓到\(O(1)\)的技巧
LCA與樹序列化
點 | 1 | 2 | 5 | 2 | 6 | 2 | 7 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
深度 | 0 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 0 |
LCA與樹序列化
點 | 1 | 2 | 5 | 2 | 6 | 2 | 7 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
深度 | 0 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 0 |
A與B的LCA是:
第一個A與最後一個B構成的區間中,深度最小的點
區間最小值
點 | 1 | 2 | 5 | 2 | 6 | 2 | 7 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
深度 | 0 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 0 |
觀察到剛剛的特性後,就能把LCA問題變成RMQ問題
不過有學過\(O(n)\)預處裡\(O(1)\)查詢的資料結構嗎?
Sparse Table
- 可以在\(O(n\log n)\)預處理,\(O(1)\)查詢的資料結構
st[x][y] = \(a_x\)到\(a_{x+2^y-1}\)的答案(共\(2^y\)個元素)
查詢任意範圍a~b,只要查詢
$$min\begin{cases} st[a][c] \\ st[b-2^c][c]\end{cases} ,c=\lfloor\log_2(b-a+1)\rfloor$$
c=std::__lg(b-a+1);
騙我!
- 這方法沒有\(O(n)\)預處裡阿!
- 別急,還有其他方法
建表法
- 因為數列相鄰的數字只有\(+1,-1\)兩種可能
- 可以考慮數列的差分序列
點 | 1 | 2 | 5 | 2 | 6 | 2 | 7 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
深度 | 0 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 0 |
+1 | +1 | -1 | +1 | -1 | +1 | -1 | -1 |
建表法
- 對於一個差分序列還原出來的原始數列,最小值的位置是固定的!結果與原數列無關
若\(a,b,c,d,e\)的差分序列是
\(<1,-1,-1,-1,>\)
最小值一定是\(e\)
建表法
- 預先把所有可能的差分序列最小值的位置建表
- 因為差分序列只有兩種數字,可以用二進位表示
\(O(n2^n)預處裡\)
\(O(1)查詢\)
看起來超級爛?
分塊大法
- 如果把數列每\(K\)個資料切一塊,預先算出每一塊的最小值
\(O(\frac{N}{K}+K)\)找出最小值
\(O(N)\)預處裡
點 | 1 | 2 | 5 | 2 | 6 | 2 | 7 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
深度 | 0 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 0 |
0 | 1 | 1 | 1 | 0 |
---|
分塊大法+Sparse Table
- 把大塊的資料做Sparse Table,就能在\(O(1)\)時間算大塊最小值
\(O(1+K)\)找出最小值
\(O(\frac{K}{N}\log \frac{K}{N} +N)\)預處裡
0 | 1 | 1 | 1 | 0 |
---|
分塊大法+Sparse Table+建表法
- 把小塊的資料做建表法,就能在\(O(1)\)時間算小塊最小值
\(O(1+1)\)找出最小值
\(O(\frac{K}{N}\log \frac{K}{N} + K2^K+ N)\)預處裡
點 | 1 | 2 | 5 | 2 | 6 | 2 | 7 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|
深度 | 0 | 1 | 2 | 1 | 2 | 1 | 2 | 1 | 0 |
數學的時間
\(O(\frac{K}{N}\log \frac{K}{N} + K2^K+ N)\)
K要怎麼設才能讓複雜度變好?
數學的時間
\(O(\frac{K}{N}\log \frac{K}{N} + K2^K+ N)\)
\(K=c\log N\)
數學的時間
\(O(\frac{K}{N}\log \frac{K}{N} + K2^K+ N)\)
\(K=c\log N\)
\(\frac{K}{N}\log \frac{K}{N}\\=\frac{c\log N}{N}\log \frac{c\log N}{N}\\=\frac{c\log N}{N}(\log{c\log N}-\log{N})=o(N)\)
\(O\)表示小於等於的話,\(o\)就表示小於
數學的時間
\(O(\frac{K}{N}\log \frac{K}{N} + K2^K+ N)\)
\(K=c\log N\)
\(K2^K\\=c\log{N}2^{c\log{N}}=c\log{N}2^{\log{N^c}}\\=c\log{N}\times N^c\)
數學的時間
\(O(\frac{K}{N}\log \frac{K}{N} + K2^K+ N)\)
\(K=c\log N\)
\(c\log{N}\times N^c\)
設\(c=0.5\)
\(0.5\log{N}\sqrt{N}\leq 0.5\sqrt{N}\sqrt{N}\in O(N)\)
LCS
分塊大法+Sparse Table+建表法
- 把K設為\(0.5\log{N}\)
\(O(1)\)找出最小值
\(O(N)\)預處裡
樹鏈剖分
樹鏈剖分
- 利用尤拉路徑做序列化,能解決子樹有關的問題,但路徑問題就比較麻煩了
- 樹練剖分是另一種把樹轉換成序列操作的方法
樹序列化-樹練剖分
把樹分解成許多線段,查詢時把幾個線段上的答案合併
要怎麼切線段?
Robert Tarjan's
Heavy Light Decomposition
- 將非葉節點的點,選擇重邊連結
- 其餘的邊為輕邊,會被分解掉
輕重邊
- 重邊:最多子節點的子樹
- 輕邊:不是重邊的邊
參閱掛長的動畫
Graph
By sylveon
Graph
- 1,430