方法
(1)向上標記法O(n)
這個方法很暴力,沒什么說的,如果有m次查詢,那時間復雜度就會是O(nm)
(2)倍增
步驟:
1.初始化:通過dfs初始化兩個數組depth[],fa[i,j];
depth[i]
:表示深度
fa[i,j]
:表示從i
開始,向上走\(2^j\)步所能走到的節點編號(\(0 \leq j \leq logn\))
哨兵:如果從i
開始跳\(2^j\)步會跳過根節點,那么fa[i,j]=0,depth[0]=0
;
2.查詢
[1]現將兩個點同時調到同一層
[2]讓兩個點同時往上跳,一直跳到它們的最近公共祖先的下一層
預處理 O(nlogn)
查詢O(logn)
具體代碼實現可以看這里 AcWing1172.祖孫詢問
(3)Tarjan——離線求LCA O(n+m)
在優先遍歷時,將所有點分為三大類:
[0] 還未搜索
過的點
[1] 正在搜索
的分支
[2] 已經遍歷
過,且回溯過
的點。
步驟:
1.在進入遞歸層
時,將點標記為1
2.搜索所有沒有遍歷過的鏈接且與該點鏈接的點,搜索回溯后
,完成集合合並
。
3.將所有與該層點有關系的詢問,全部遍歷,當另一個點已經被標記為2
,則找到了最近公共祖先,就是另一個點的並查集標志節點。
4.最后回溯時
,將該節點標記為2
.
注意:
一定要先將要拓展的點進行拓展,回溯時再進行集合合並。記憶的話,就記住,我們進行集合合並的點一定是被回溯過的。理解的話,可以看這個例子。
比如正在遍歷的一條路徑上a->b->c->d
剛遍歷完c節點的子樹並回溯到c節點
那么假如有一個詢問是在c-d
節點之間,對於d節點st[d]=2;
如果先p[j]=u
,那么p[a]=a,p[b]=a,p[c]=b
那么進行anc=find(d)
操作時會把d
的祖先p[d]
直接路徑壓縮成a
節點。
如果后p[j]=u
,由於p[c]=c
,那么在進行anc=find(d)
操作時p[d]
是c
節點
顯然,我們要找的是最近公共祖先,那就要先遍歷再合並。
其實就是,如果我們提前進行合並那同一路上的公共祖先就會出現問題。