樹上最近公共祖先(LCA)的算法
有錯請大力指出【鞠躬】第一次寫正經博客非常慌張
LCA(Least Common Ancestors),即最近公共祖先,是指在有根樹中,找出某兩個結點u和v最近的公共祖先。
對於有根樹T的兩個結點u、v,最近公共祖先LCA(T,u,v)表示一個結點x,滿足x是u、v的祖先且x的深度盡可能大。
另一種理解方式是把T理解為一個無向無環圖,而LCA(T,u,v)即u到v的最短路上深度最小的點。
——百度百科
LCA的四種算法:
- 記錄dfs序轉化為rmq問題(st表)
- tarjan算法
- 倍增算法
- 樹鏈剖分
一、記錄dfs序轉化為rmq問題
1.dfs序是什麽?
其實本人對dfs序的定義也不怎麽清晰……望告知orz
首先我們需要一顆樹……比如說它長這樣:
定義rt(root)為樹根,則在這棵樹中rt = 1。別說了我知道圖上是A
本題需要用到的dfs序需在進入每個節點即搜完某一個兒子的時候記錄這個節點。從rt開始進行dfs(深度優先搜索),則得到的dfs序列為A B D B E B F B A C G H G C A。
且讓我們跟著跑一遍。
1.dfs(A) , 序列為A
2.找到A的兒子B,dfs(B),序列為A B
3.找到B的兒子D,dfs(D),序列為A B D
4.發現D沒有兒子,返回B,序列為A B D B
5.6.7.8. dfs(E),返回B,dfs(F),返回B,序列為A B D B E B F B
9.搜完B的兒子,返回A,序列為A B D B E B F B A
10.11.12. dfs(C),dfs(G),dfs(H),序列為A B D B E B F B A C G H
13.14.15 返回G,返回C,返回A,序列為A B D B E B F B A C G H G C A
結束。
定義x為當前節點標號,dep為當前深度,h[i]為樹高,in[i]為每個節點被訪問到的序號,d[i]為dfs序。
代碼如下:
void dfs(int x , int dep) { d[++ cnt] = x ; h[x] = dep , inView Code[x] = cnt ; for(int i = fst[x] ; i ; i = e[i].nx) { if(e[i].x == fa[x]) continue ; fa[e[i].x] = x ; dfs(e[i].x , dep + 1) ; d[++ cnt] = x ; } }
除了搜完根節點後結束dfs外,每個點被搜到和退出時均會在dfs序中加入一個數,因此該dfs序的長度為2 * n - 1。
設點u、點v的最近公共祖先為點x,並且u在v之前被dfs到。因為x為u、v的祖先,所以dfs時先找到x,再從x往下找到u,即dfs(u)時已經過了x。記錄u後,返回到x再向下找到v。故在dfs序中,in[u]和in[v]之間必出現過x。而dfs(v)後返回時,x的子樹還沒有被搜完,所以不會出現x的祖先。這樣即可保證在in[u]、in[v]中高度最小的點即為u、v的最近公共祖先。
這樣我們有了一個正確的算法,但如果對於每個詢問區間,都一位一位尋找最值,效率為O(mn),顯然不能接受。
樹上最近公共祖先(LCA)的算法