最近公共祖先(LCA)
在我们做有关树的题时,有时会遇到这样的问题:我们希望知道距离两个子结点A和B最近的祖先是谁。

一.暴力
我们可以很容易地想到,可以让节点A和B同时向上搜索。两个点第一次都经过的节点便是我们要找的答案。
但是,这样做的效率太低了。一旦数据开大点或者开成一条链就会寄。所以,我们想一想有没有什么可以优化的地方
二.优化
节点A和B的层数可能相等也可能不相等。不妨设A的层数<=B的层数。我们知道:两个节点的公共祖先的层数一定$\leq $A的层数。所以我们可以把B先跳到与A层数相同,再同时向上搜索。
但是,这样仍然不能解决问题。每次只向上跳一步太慢了,我们应该找到一种能够快速跳到想要的位置的方法。
三.倍增
我们可以用倍增的思想来解决这一问题。

如图,我们将任意节点(以16节点为例)到根节点的路径进行二进制拆分,分成\(2^0、2^1、2^2...\)几段。在每个节点中存入它的\(2^n\)的祖先。用\(f[i][j]\)表示节点i的\(2^j\)祖先,则状态转移公式为
\(f [i] [j] = f [ f [i] [j-1] ] [j-1]\\\)
然后,每次我们跳的时候就先预看两个节点能跳到的最远点,如果目的地不相等就跳过去,并用新的f数组接着看;如果目的地相等就不跳,改为看距离缩短一半的目的地,直到将f数组跳完。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,s,x,y;
int f[500010][20];//储存每个点的2^i祖先
int dep[500010];//每个点的深度
vector<int> tmap[500010];
void dfs(int be,int numb,int fat)//从根节点开始把树梳一遍
{
dep[be]=numb;
f[be][0]=fat;
for(int i=1;i<=log2(numb);i++)
f[be][i]=f[f[be][i-1]][i-1];//求f数组
for(auto i:tmap[be])
{
if(i==fat)continue;
dfs(i,numb+1,be);
}
}
int lca(int x,int y)//求节点x和y的最近公共祖先
{
if(dep[x]<dep[y])swap(x,y);//规定x为层数较大的节点(即在较下面)
for(int i=19;i>=0;i--)//将x跳到与y同层
{
if(dep[f[x][i]]>=dep[y])x=f[x][i];
if(x==y)return x;
}
for(int i=19;i>=0;i--)//x和y一起向上以倍增的形式跳
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
tmap[x].push_back(y);
tmap[y].push_back(x);
}
dfs(s,1,0);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
posted on
浙公网安备 33010602011771号