什么是最近公共祖先呢?其实这是在树上定义的,比如说x节点,那么x节点以及x节点的父节点以及x节点的父节点的父节点的……父节点,这都算是x的祖先,而公共祖先显然是对于两个节点而言的,就是某个节点同时是两个节点的祖先,那么这个节点就是两个节点的公共祖先,最近公共祖先当然是离他们最近的公共祖先了。画个图来说明一下:
显然C和D都是A和B的公共祖先,由于C离A和B距离更近,所以C就是A和B的最近公共祖先,下面我主要来说一下A和B的公共祖先应该怎么求,首先我们需要求出所有点的深度,这个用一个搜索就可以直接得到,这个不必多说,除此之外,我们还需要预处理出来一个数组f,f[i][j]表示以i为节点向上找2^j个父亲节点所得到的父亲节点,比如f[A][1]就是A节点上面找2^1个父亲节点所得到的节点C,同理f[B][2]就是D,那么这个数组怎么求出来呢?其实也不难,我们想首先我们在搜索函数中会传两个参数,一个是当前节点x,另一个就是当前节点的父亲节点father,所以当前节点的直接父节点就有了,也就是f[x][0]=father,然后我们就可以用f[x][i]=f[f[x][i-1]][i-1]来更新了,因为我们函数是从根节点向下搜索的,所以我们一定在用求f[x][i]节点时,f[f[x][i-1]][i-1]肯定已经更新完成了,所以我们就可以用更新完的值去更新当前f数组,更新思想类似于ST表更新思想。
具体代码:
d[x]=d[father]+1;//计算深度
f[x][0]=father;
for(int i=1;i<=20;i++)
f[x][i]=f[f[x][i-1]][i-1];
好,现在我们得到了f数组以及深度d数组,下面该说怎么用这两个数组来求最近公共祖先了。
首先我们需要将A和B中深度较大的点移至与B同一高度,图中显然是B深度更大,我们不妨假设A与B之间的深度差为k,其二进制表示为1101,那么我们可以先将B向上移动2^3=8格,然后A与B之间的深度差就变为0101,然后再将B向上移动2^2=4格,然后A与B之间的深度差就变为0001,最后再将B向上移动2^0=1格,然后A与B就在同一高度了,我们就是按照这样的思想将A和B移至同一高度的,那么如何将B移动2^i格呢,其实这个也就用到了我们的f数组,我们令x=f[x][i]不就实现了将B向上移动2^i格了吗?至此我们也已经实现了将B与A移至同一层次了。
相应代码:
for(int i=20;i>=0;i--)//需要先将x和y移至同一高度
if(d[f[x][i]]>=d[y])
x=f[x][i];
下面就相当于已知A和B在同一层次然后求A和B最近公共祖先了,我们先来看一下A和B的公共祖先满足什么性质吧,其中最明显的一个性质就是如果T是A和B的公共祖先,那么T的祖先节点就是A和B的公共祖先,所以我们可以从下往上找,直到找到一个节点其父节点就是A和B的最近公共祖先。这里的方法和上面将A和B移至同一高度的方法类似,但又有一些不同,就是我们依旧是从高位开始for循环,当满足向上移动2^j格后得到的节点不是两者公共祖先时就移动,如果是就不移动,其实原因很简单,因为如果是公共祖先就移动的话,那么for循环的第一次可能就直接移动了,虽然是其公共祖先,但却不是最近的,所以我们的目的是先找出来A和B最近公共祖先下面的一个节点,然后直接通过f[x][0]找到其最近公共祖先即可。
相应代码:
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
最后需要注意的一点就是我们最后找到的是A和B最近公共祖先的下一个节点,这也就意味着我们需要输出f[x][0],但是如果将A和B移至同一层次后发现A和B是同一个节点,也就是说A和B的公共节点是A和B其中一个节点,这时候就要直接输出了,所以要记得特判这种情况。
树上倍增题目常常可能会加入一些数组dp[i][j]表示从i节点到i节点上面的第2^j个父节点这段区间的某种性质(比如说最大值,最小值之类的)
下面给出一道例题并给出其代码:
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N-1行每行包含两个正整数x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。
输出格式
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入
5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5
输出
4 4 1 4 4
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e6+10;
int f[N][21],e[N],ne[N],h[N],idx,d[N];
void add(int x,int y)
{
e[idx]=y;
ne[idx]=h[x];
h[x]=idx++;
}
void dfs(int x,int father)
{
d[x]=d[father]+1;//计算深度
f[x][0]=father;
for(int i=1;i<=20;i++)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(j==father) continue;
dfs(j,x);
}
}
int main()
{
int n,m,s;
cin>>n>>m>>s;
int u,v;
memset(h,-1,sizeof h);
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(s,0);
int x,y;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
if(d[x]<d[y]) swap(x,y);
for(int i=20;i>=0;i--)//需要先将x和y移至同一高度
if(d[f[x][i]]>=d[y])
x=f[x][i];
if(x==y)
{
printf("%d\n",x);
continue;
}
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
printf("%d\n",f[x][0]);
}
return 0;
}