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