前言
這篇文章,是一篇總結倍增查找LCA的一篇文章,同時也是實現在淺談RMQ算法文章中承諾.若有疑問或建議,請於下方留言,謝謝!
LCA是什麼?
LCA(Least Common Ancestors),即最近公共祖先,是指在有根樹中,找出某兩個結點u和v最近的公共祖先
如何實現LCA?
- 暴力直接找
- 首先將u,v中深度較深的那個點蹦到和較淺的點同樣的深度
- 然後兩個點一起向上蹦直到蹦到同一個點,這個點就是它們的LCA
- 時間複雜度在極端條件下可以到O(N)
- 運用DFS序
- DFS序就是用DFS方法遍歷整棵樹得到的序列。
- 兩個點的LCA一定是兩個點在DFS序中出現的位置之間深度最小的那個點
- 尋找最小值可以用使用我們前篇文章講的RMQ
- 運用倍增思想
- 我們令father[i][j]表示編號爲i的點,往上蹦2^j次的父親是誰,其實這個預處理和RMQ很相似,先從大到小枚舉j,然後令father[i][j]=father[father[i][j-1]][j-1],這樣預處理的時間複雜度爲O(NlogN)
- 接下來是查詢,可以分兩步走
- 將u和v移動到同樣的深度
- u和v同時向上移動,直到重合。第一個重合的點即爲LCA
- 再是移動到同樣的深度
- 令u爲深度較大的點。我們從
log2n , 到0枚舉,令枚舉的數字爲j。如果從u向上跳2j 步小於了v的深度,不動;否則向上跳2j 步。這樣一定能移動到和v一樣的深度。 - 假設一共要跳k步,上面的算法相當於枚舉k的每個2進製爲是0還是1
- 令u爲深度較大的點。我們從
- 從同樣的深度移動到同一個點
- 和上一步類似。從
log2n 到0枚舉,令枚舉的數字爲j。如果兩個向上跳2j 步將要重合,不動,否則向上跳2j 步。 - 通過這種辦法u和v一定能夠到達這樣一種狀態——它們當前不重合,如果再向上一步就重合。所以再上一步就得到了LCA
- 和上一步類似。從
- 由於本質上是枚舉每一個二進制位,所以單次查詢的複雜度爲
log2n
我們可以注意到,在整個倍增查找LCA的過程中,從u到v的整條路徑都被掃描了一遍。如果我們在倍增數組F[i][j]中再記錄一些別的信息,就可以實現樹路徑信息的維護和查詢
LCA模板題
這裏給一個LCA用倍增思想的模板,題目來源codevs4605 (傳送門)
題目描述 Description
顧名思義.給一棵有根樹,以及一些詢問,每次詢問樹上的2 個節點A、B,求它們的最近公共祖先.
輸入描述 Input Description
第一行一個整數N.
接下來N 個數,第i 個數Fi 表示i 的父親是Fi. 若Fi = 0,則i 爲樹根.
接下來一個整數M.
接下來M 行,每行2 個整數A、B,詢問節點(A xor LastAns)、(Bxor LastAns)的最近公共祖先. 其中LastAns 爲上一個詢問的答案,一開始LastAns = 0.
輸出描述 Output Description
對每一個詢問輸出相應的答案.
樣例輸入 Sample Input
10
0 1 2 3 2 4 2 5 4 9
10
3 9
2 7
7 8
1 1
0 6
6 11
6 3
10 7
2 15
7 7
樣例輸出 Sample Output
3
1
4
5
2
4
2
5
2
5
數據範圍及提示 Data Size & Hint
30% n,m≤1000
100% n,m≤100,000
代碼
不解釋了吧,上代碼
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int n,m;
vector <int> a[100001];
int f[100001][18];
int father[100001];
int root;
int height[100001];
int lca(int x,int y)
{
if(height[x]<height[y])
{
int tt=x;
x=y;
y=tt;
}
int t=0;
while((1<<t) <= height[x])t++;
t--;
int i;
for(i=t;i>=0;i--)
{
if(height[x]-(1<<i)>=height[y])
{
x=f[x][i];
}
}
if(x==y)return x;
for(i=t;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
void dfs(int x,int deep)
{
int i,j;
height[x]=deep;
for(i=1;i<=17;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
}
for(i=0;i<a[x].size();i++)
{
dfs(a[x][i],deep+1);
}
}
int main()
{
scanf("%d",&n);
int i,j;
for(i=1;i<=n;i++)
{
scanf("%d",&father[i]);
f[i][0]=father[i];
if(f[i][0]==0)
{
root=i;
}
a[father[i]].push_back(i);
}
dfs(root,0);
scanf("%d",&m);
int ans=0;
int x,y;
for(i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
x=x^ans;y=y^ans;
ans=lca(x,y);
printf("%d\n",ans);
}
return 0;
}