前言
这篇文章,是一篇总结倍增查找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
- 从同样的深度移动到同一个点
- 和上一步类似。从 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;
}

1729

被折叠的 条评论
为什么被折叠?



