倍增法求LCA

来源:互联网 发布:养匪自重知乎 编辑:程序博客网 时间:2024/04/30 21:53

何为LCA

就像节目开始之前主持人总要念段广告一样,在这里我要先介绍何为LCA
LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。

倍增法

顾名思义,取“成倍地增加”之义
引入倍增法往往可以将O(n)进化为O(log(n))


倍增法求LCA

注意:请务必搭配手动模拟食用

初始化

分两步

  1. 深搜遍历树,更新每个点的深度
  2. 然后用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的第2j1个父亲 的第2j1个父亲即 2j=2j12j1

需要注意的是,外层循环j代表2的j次幂,这样的话,就可以保证 i的第2j1个父亲 的第2j1个父亲 是已知的

求LCA

求LCA大抵分两步

  1. 调平
  2. 求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

这个式子需要详细说明一下:

  1. 首先是位运算,不懂的同学建议详细学一下,很神奇的,由于本人讲不清楚故不再赘述
  2. 从度娘百科回来的你不难发现“&”符可以用来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


强烈建议画图模拟~
举个例子~

233

原创粉丝点击