C 最近公共祖先 LCA

2021-10-03 06:43:49 字數 3715 閱讀 7147

例題:

對於有根樹t的兩個結點u、v,最近公共祖先lca(t,u,v)表示乙個結點x,滿足x是u、v的祖先且x的深度盡可能大。

紅色的都是是a和b的公共祖先,但只有最近的c才是最近公共祖先。

lca問題是樹上的乙個經典問題,在很多方面有著廣泛的應用,比如求lcp(最長公共字首),接下來我們就來介紹他的幾種演算法。

如果我們要求a和b的最近公共祖先,就沿著父親的方向把a的所有祖先都標記一下(類似並查集找父親,但是沒有路徑壓縮),然後在從b開始往上找祖先,碰到第乙個被標記的點,就是a和b的最近公共祖先。

c是最近公共祖先。

求乙個對點的lca時間複雜度高達o(n)。

求m個點對的lca時間複雜度高達o(mn)。

當m和n都高達10萬的時候,超時了!!!

寶寶難以承受!!!!!

簡單的介紹一下tarjan演算法:

tarjan演算法是離線演算法,它必須先將所有的要查詢的點對存起來,然後在搜的時候輸出結果。

tarjan演算法很經典,因為演算法的思想很巧妙,利用了並查集思想,在dfs下,將查詢一步一步的搜出來。

基本思路:

下面給出真**:

int f[n]

,n,m,ans[n]

,check[n]

; vector<

int> a[n]

,b[n]

,id[n]

;int

find

(int x)

void

tarjan

(int x)

}for

(int i=

0; i

.size()

; i++

)}

我們在深度優先遍歷的時候,先遍歷x節點的左子樹,當遍歷到u的時候,發現v沒有被遍歷過,那麼就不去管lca(u,v)這個問題,然後我們把已經遍歷的x子樹的所有節點都合併到他的父親(即father指向父親),然後當我們遍歷到v的時候,發現u已經遍歷過了,那麼此時u在並查集裡的father就是u和v的最近公共祖先.

時間複雜度:由於每個點只遍歷一次,每個問題只列舉2次,所以時間複雜度是o(n+2mα(n))。α(n)為並查集查詢一次根所需要的時間。

首先乙個小問題,給你兩個點a和b,你如何快速的回答這兩個點在樹裡面是否具有祖先和後代的關係。

暴力演算法又是o(n),明顯太浪費時間!

引入時間戳的概念:所謂的時間戳就是在給一棵樹進行深度優先遍歷的同時,記錄下計入每個點的時候和離開每個點的時間。

如圖所示,每個節點的左邊是進入的時間,右邊是離開的時間。

如果a是b的祖先,只要滿足 (in[a]<=in[b]) and (out[b]<=out[a])

也就是我們只需要一次深搜,接下來對於任何詢問a和b是否有祖先關係的時候,我們只要o(1)的時間就能回答這個問題。

建立倍增陣列:

定義f[i][j]為與節點i距離為2^j的祖先的編號。

明顯的f[i][0]就是每個點直接的父親。

另有遞推關係:f[i][j]=f[f[i][j-1],j-1]。

於是我們只需要在nlogn的時間內就可以求出f陣列的值。

如果f[i][j]不存在,我們就令f[i][j]=根,方便我們計算

接下來如何求a和b的最近公共祖先呢?

1、如果a是b的祖先,那麼輸出a

2、如果b是a的祖先,那麼輸出b

3、for i:=20 downto 0 do

if f[a][i]不是b的祖先,那麼令 a=f[a][i];

迴圈結束的時候,f[a][0]就是最近公共祖先。

int

lca(

int x,

int y)

codevs1036 商務旅行(可惜codevs崩潰了)

某首都城市的商人要經常到各城鎮去做生意,他們按自己的路線去做,目的是為了更好的節約時間。

假設有n個城鎮,首都編號為1,商人從首都出發,其他各城鎮之間都有道路連線,任意兩個城鎮之間如果有直連道路,在他們之間行駛需要花費單位時間。該國公路網路發達,從首都出發能到達任意乙個城鎮,並且公路網路不會存在環。

你的任務是幫助該商人計算一下他的最短旅行時間。

輸入檔案中的第一行有乙個整數n,1<=n<=30 000,為城鎮的數目。下面n-1行,每行由兩個整數a 和b (1<=a, b<=n; a<>b)組成,表示城鎮a和城鎮b有公路連線。在第n+1行為乙個整數m,下面的m行,每行有該商人需要順次經過的各城鎮編號。m<=30000

在輸出檔案中輸出該商人旅行的最短時間。

5

1 21 5

3 54 541325

7
#include

#include

#include

#include

#include

#include

#define ri register int

#define re(i,a,b) for(ri i=a; i<=b; i++)

#define ms(i,a) memset(a,i,sizeof(a))

using

namespace std;

typedef

long

long ll;

intconst n=

30005

;struct edge e[n<<1]

;int n,m,cnt,sum,ans;

int h[n]

,vis[n]

,tin[n]

,tout[n]

,dep[n]

;int f[n][15

];intast

(int x,

int y)

intlca

(int x,

int y)

void

add(

int a,

int b)

void

dfs(

int x,

int fa,

int d)

tout[x]

=++sum;

}int

main()

dfs(1,

1,0)

;re(j,1,14

)re(i,1

,n)

f[i]

[j]=f[f[i]

[j-1]]

[j-1];

scanf

("%d"

,&m)

;int k=1;

re(i,

1,m)

printf

("%d\n"

,ans)

;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的樹,求某兩個點的最近公共祖先。思路 預處理出每個點的深度再一...