**:
絕對好文
tarjan演算法的步驟是(當dfs到節點u時):
1 在並查集中建立僅有u的集合,設定該集合的祖先為u
1 對u的每個孩子v:
1.1 tarjan之
1.2 合併v到父節點u的集合,確保集合的祖先是u
2 設定u為已遍歷
3 處理關於u的查詢,若查詢(u,v)中的v已遍歷過,則lca(u,v)=v所在的集合的祖先
舉例說明(非證明):
假設遍歷完10的孩子,要處理關於10的請求了
取根節點到當前正在遍歷的節點的路徑為關鍵路徑,即1-3-8-10
集合的祖先便是關鍵路徑上距離集合最近的點
比如此時:
1,2,5,6為乙個集合,祖先為1,集合中點和10的lca為1
3,7為乙個集合,祖先為3,集合中點和10的lca為3
8,9,11為乙個集合,祖先為8,集合中點和10的lca為8
10,12為乙個集合,祖先為10,集合中點和10的lca為10
你看,集合的祖先便是lca吧,所以第3步是正確的
道理很簡單,lca(u,v)便是根至u的路徑上到節點v最近的點
為什麼要用祖先而且每次合併集合後都要確保集合的祖先正確呢?
因為集合是用並查集實現的,為了提高速度,當然要平衡加路徑壓縮了,所以合併後誰是根就不確定了,所以要始終保持集合的根的祖先是正確的
關於查詢和遍歷孩子的順序:
wikipedia上就是上文中的順序,很多人的**也是這個順序
但是網上的很多講解卻是查詢在前,遍歷孩子在後,對比上文,會不會漏掉u和u的子孫之間的查詢呢?
不會的如果在剛dfs到u的時候就設定u為visited的話,本該回溯到u時解決的那些查詢,在遍歷孩子時就會解決掉了
這個順序問題就是導致我頭大看了很久這個演算法的原因,也是絮絮叨叨寫了本文的原因,希望沒有理解錯= =
最後,為了符合本blog風格,還是貼**吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int
f[maxn],fs[maxn];
//並查集父節點 父節點個數
bool
vit[maxn];
int
anc[maxn];
//祖先
vector<
int
> son[maxn];
//儲存樹
vector<
int
> qes[maxn];
//儲存查詢
typedef
vector<
int
>::iterator it;
int
find(
int
x)
void
union(
int
x,
int
y)
void
lca(
int
u)
vit[u]=
true
;
for
(it v=qes[u].begin();v!=qes[u].end();++v)
}
ref:
LCA離線演算法tarjan
lca演算法 lca least common ancestor 是指在一棵樹中,距離兩個點最近的兩者的公共節點。也就是說,在兩個點通往根的道路上,肯定會有公共的節點,我們就是要求找到公共的節點中,深度盡量深的點。還可以表示成另一種說法,就是如果把樹看成是乙個圖,這找到這兩個點中的最短距離。本文先介...
LCA 離線tarjan演算法
對於最近公共祖先問題,我們先來看這樣乙個性質,當兩個節點 u,v 的最近公共祖先是x時,那麼我們可以確定的說,當進行後序遍歷的時候,必然先訪問完x的所有子樹,然後才會返回到x所在的節點。這個性質就是我們使用tarjan演算法解決最近公共祖先問題的核心思想。同時我們會想這個怎麼能夠保證是最近的公共祖先...
Tarjan演算法求LCA
題源 這個題還是debug了好久。1.呼叫函式中如果要更改外部資料需要傳遞引用,其實傳遞引用往往效率更高,以後要多加注意這一點。2.忘寫並查集了 捂臉逃 3.題目要求的是距離而不是lca,認真審題。貼 include include include define maxn 100005 define...