何为LCA
就像节目开始之前主持人总要念段广告一样,在这里我要先介绍何为LCA
LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。
倍增法
顾名思义,取“成倍地增加”之义
引入倍增法往往可以将O(n)进化为O(log(n))
倍增法求LCA
注意:请务必搭配手动模拟食用
初始化
分两步
- 深搜遍历树,更新每个点的深度
- 然后用f[i][j]记录点i的第2^j次幂的祖先是谁
更新f[i][j]数组代码如下
void pre(){
maxh=log(n)/log(2)//以2为低n的对数(换底公式可证)
for(int j=1;j<=maxh;j++)
for(int i=1;i<=N;i++)
fa[i][j]=fa[fa[i][j-1]][j-1];
}
具体来讲,更新的思路是:i的第 2j 个父亲是 i的第 2j−1 个父亲 的第 2j−1 个父亲即 2j=2j−1∗2j−1
需要注意的是,外层循环j代表2的j次幂,这样的话,就可以保证 i的第 2j−1 个父亲 的第 2j−1 个父亲 是已知的
求LCA
求LCA大抵分两步
- 调平
- 求LCA
调平是指将所求两点调到相同深度
因为要保证LCA算法整体复杂度在O(log(n))水平,因此一般的方法(O(n)+)是不可行的
所以在此介绍倍增的调平办法
先放代码
if(deep[x]<deep[y]) swap(x,y);//保证x的深度大于y的深度
int d=deep[x]-deep[y];
for(int i=0;i<=maxh;i++){
if((1<<i)&d) x=fa[x][i];
}
(1<<i)d
这个式子需要详细说明一下:
- 首先是位运算,不懂的同学建议详细学一下,很神奇的,由于本人讲不清楚故不再赘述
- 从度娘百科回来的你不难发现“&”符可以用来2进制分解;而 (1<<i) 则等价于 2i ——这样一切的一切就越发清晰了:该循环的目的是对d二进制分解,并在分解的途中让x向上跳
LCA剩下的代码是这样的
if(x==y) return x;//如果调平之后x、y重合,则y即为所求
for(int j=maxh;j>=0;j--){
if(fa[x][j]!=fa[y][j]){
x=fa[x][j];
y=fa[y][j];
}
}
return fa[x][0];
如果最大距离时两点祖先相同,则LCA必然在两点和最大距离之间(废话)
但是在从最远向近处跳的同时我们也在缩小LCA可能存在的区间上限
当跳到某深度,x、y的祖先不是一个了,就将x、y跳到该深度(缩短下限)
由于区间每次都缩小一半,所以该for复杂度为O(log(n))
值得注意的是,x、y最后会停在LCA的儿子上,所以他们的父亲就是我们苦苦寻找的LCA
强烈建议画图模拟~
举个例子~