最近公共祖先(LCA)

2021-09-20 15:32:11 字數 3774 閱讀 7195

一、基本概念:

給定一棵有根樹,若節點z既是節點x的祖先,也是節點y的祖先,則稱z是x,y的公共祖先。在x,y的所有公共祖先中,深度最大的乙個稱為x,y的最近公共祖先,記為lca(x,y)。

lca(x,y)是x到根的路徑與y到根的路徑的交匯點。它也是x與y之間的路徑上深度最小的節點。求最近公共祖先的方法通常有五種。

在此介紹三種,另外兩種還沒學。

二、向上標記法:

從x向上走到根節點,並標記所有經過的節點。

從y向上走到根節點,當第一次遇到已標記的節點時,就找到了lca(x,y)。

對於每個詢問,向上標記法的時間複雜度最壞為o(n)。

三、樹上倍增法:

樹上倍增法是乙個很重要的演算法。除了lca之外,它在很多問題中都有廣泛應用。設f(x,k)表示x的2k輩祖先,即從x向根節點走2k步到達的節點。特別的,若該節點不存在,則令f(x,k)=0。f(x,0)就是x的父節點。除此之外,1≤k≤logn ,f(x,k)=f(f(x,k-1),k-1)。

這類似於乙個動態規劃的過程,階段就是節點的深度。因此,我們可以對樹進行廣度優先遍歷,按照層次順序,在節點入隊之前,計算它在f陣列中相應的值。

以上是預處理部分,時間複雜度為o(nlogn),之後可以多次對不同的x,y計算lca,每次詢問的時間複雜度為o(logn)。

基於f陣列計算lca(x,y)分為以下幾步:

(1)----設d(x)表示x的深度。不妨設d(x)≥d(y)。(否則可以交換x,y)。

(2)----用二進位制拆分思想,把x向上調整到於y同一深度。

具體來說,就是依次嘗試從x向上走k=2logn,……21,20步,檢查到達的節點是否比y深。在每次檢查中,若是,則令x=f(x,k)。

(3)----若此時x==y,說明已經找到了lca,lca就是y。

(4)----用二進位制拆分思想,把x,y同時向上調整,並保持深度一致且二者不相匯。

具體來說,就是依次嘗試把x,y同時向上走k=2logn,……21,20步,在每次嘗試中,

若f(x,k)≠f(y,k)(即仍未相會),則令x=f(x,k),y=f(y,k)。

(5)----此時x,y必定只差一步就相會了,他們的父節點f(x,0)就是lca。

以hdoj2586 how far away為例:

時間複雜度為:o((n+m)logn)。

#include

#include

#include

#include

#include

#include

#include

#define ll long long

using

namespace std;

const

int maxn=

50010

;int f[maxn][20

],d[maxn]

,dis[maxn]

;int ver[maxn*2]

,nt[maxn*2]

,edge[maxn*2]

,head[maxn]

;int t,n,m,tot,t;

queue<

int>q;

void

add(

int x,

int y,

int z)

void

bfs(

void)}

}int

lca(

int x,

int y)

if(x==y)

return x;

for(

int i=t;i>=

0;i--

)return f[x][0

];}int

main

(void

)bfs()

;for

(int i=

1;i<=m;i++)}

return0;

}

四、lca的tarjan演算法:

tarjan演算法本質上是使用並查集對「向上標記法」的優化。它是乙個離線演算法,需要把m個詢問一次性讀入,統一計算,最後統一輸出。時間複雜度o(n+m)。

在深度優先遍歷的任意時刻,樹中節點分為三類:

(1)----已經訪問完畢並且回溯的節點。在這些節點上標記乙個整數2。

(2)----已經開始遞迴,但尚未回溯的節點。這些節點就是當前正在訪問的節點x以及x的祖先。在這些節點上標記乙個整數1。

(3)----尚未訪問的節點。這些節點沒有標記。

對於正在訪問的節點x,它到根節點的路徑已經標記為1。若y是已經訪問完畢並且回溯的節點,則lca(x,y)就是從y向上走到根,第乙個遇到的標記為1的節點。

可以利用並查集進行優化,當乙個節點獲得整數2的標記時,把它所在的集合合併到它的父節點所在的集合中(合併時它的父節點標記一定為1,且單獨構成乙個集合)。

這相當於每個完成回溯的節點都有乙個指標指向它的父節點,只需查詢y所在集合的代表元素(並查集的get操作),就等價於從y向上一直走到乙個開始遞迴但尚未回溯的節點(具有標記1),即lca(x,y)。

此時掃瞄與x相關的所有詢問,若詢問當中的另乙個點y的標記為2,就知道了該詢問的回答應該是y在並查集中的代表元素(get(y)函式的結果)。

同樣以how far away為例:

時間複雜度為o(n+m)。

#include

#include

#include

#include

#include

#include

#include

#include

#define ll long long

using

namespace std;

const

int maxn=

50010

;int ver[maxn*2]

,nt[maxn*2]

,edge[maxn*2]

,head[maxn]

;int fa[maxn]

,d[maxn]

,v[maxn]

,lca[maxn]

,ans[maxn]

;vector<

int>query[maxn]

,query_id[maxn]

;int t,n,m,tot,t;

void

add(

int x,

int y,

int z)

void

add_query

(int x,

int y,

int id)

int_get

(int x)

void

tarjan

(int x)

for(

int i=

0;i.size()

;i++)}

v[x]=2

;}intmain

(void

) tot=0;

int x,y,z;

for(

int i=

1;i)for

(int i=

1;i<=m;i++)}

tarjan(1

);for(

int i=

1;i<=m;i++

)printf

("%d\n"

,ans[i]);

}return0;

}

最近公共祖先 LCA 最近公共祖先

直接暴力搜尋參考 普通搜尋每次查詢都需要 樸素演算法是一層一層往上找,倍增的話直接預處理出乙個 具體做法是 維護乙個 的關係來線性求出這個陣列 int anc n 31 int dep n 記錄節點深度 void dfs int u,int parent for int i 0 i g u size...

最近公共祖先 最近公共祖先(LCA)

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。輸入格式 第一行包含三個正整數n m s,分別表示樹的結點個數 詢問的個數和樹根結點的序號。接下來n 1行每行包含兩個正整數x y,表示x結點和y結點之間有一條直接連線的邊 資料保證可以構成樹 接下來m行每行包含兩個正整數a b,表示詢問...

LCA 最近公共祖先

定義 對於有根樹t的兩個結點u v,最近公共祖先lca t,u,v 表示乙個結點x,滿足x是u v的祖先且x的深度盡可能大。另一種理解方式是把t理解為乙個無向無環圖,而lca t,u,v 即u到v的最短路上深度最小的點。現在給定乙個根為1的樹,求某兩個點的最近公共祖先。思路 預處理出每個點的深度再一...