问题:输入一棵n个结点的树,以及m组请求,每个请求是两个结点,求它们的最小公共祖先。
实现思路:先建树,求出每一个点的深度,再根据深度来求lca。
具体实现:
建树求深度,并求出每个点的父节点,方便后面进行跳点、
接下来考虑如何查找最近公共祖先,可以先将两个点升到同一高度,再同时往上跳,但这么做的时间复杂度为O(nm)肯定会超时。这里我用ST表进行优化。
优化思路:下面是一个树,如果我们要求6号节点和7号节点lca,我们可以先列出一个式子,表示第i层时他们的祖先节点是否是同一个点。
如图列出式子如下:F F F T T T
可以看出当第一次达到T时,该点为两点的最近公共祖先。
那我们用ST表记录st[x][i]表示向上跳2^i个点,跳了之后如果该点为两点的祖先节点就换更小的i来跳,反之则将x,y更替为st[x][i],st[y][i]。通过这种方式来进行提速,单次lca的时间复杂度为O(logn),总体时间复杂度为O(mlogn)。
代码如下:
//st表初始化
void init(){
for(int i=1;i<=n;i++)st[i][0]=fa[i];
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i<=n;i++)st[i][j]=st[st[i][j-1]][j-1];
}
}
//lca模版
int lca(int x,int y){
if(deep[x]<deep[y]){
int tp=x;x=y;y=tp;
}int t=deep[x]-deep[y],i=0;
while(t){
if(t%2==1)x=st[x][i];
t=t>>1;i++;
}if(x==y)return x;
for(int i=logn;i>=0;i--){
if(st[x][i]==st[y][i])continue;
x=st[x][i];y=st[y][i];
}return st[x][0];
}
最后完整代码如下:
#include<cstdio>
#include<cmath>
using namespace std;
const int N=1e6+100;
struct node{
int y,nx;
}e[N];
int n,m,last,logn;
int head[N],fa[N],deep[N],st[N][30];
void add(int x,int y){
last++;
e[last].y=y;
e[last].nx=head[x];
head[x]=last;
}void dfs(int x,int _fa){
fa[x]=_fa;
deep[x]=deep[_fa]+1;
for(int i=head[x];i;i=e[i].nx){
int y=e[i].y;
if(y==_fa)continue;
dfs(y,x);
}
}void init(){
for(int i=1;i<=n;i++)st[i][0]=fa[i];
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i<=n;i++)st[i][j]=st[st[i][j-1]][j-1];
}int lca(int x,int y){
if(deep[x]<deep[y]){
int tp=x;x=y;y=tp;
}int t=deep[x]-deep[y],i=0;
while(t){
if(t%2==1)x=st[x][i];
t=t>>1;i++;
}if(x==y)return x;
for(int i=logn;i>=0;i--){
if(st[x][i]==st[y][i])continue;
x=st[x][i];y=st[y][i];
}return st[x][0];
}int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);add(b,a);
}dfs(1,0);init();
logn=log2(n);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}return 0;
}