LCA(最近公共祖先)问题

问题描述

在一棵树中,如果某个节点z是节点x的祖先(即节点z深度<节点x),也是y的祖先。那么称节点z是x与y的公共祖先。

那顾名思义,所谓最近公共祖先,就是对于x和y来说距离之和最近的公共祖先。

解法一:向上标记法

除非你发了高烧啥都不会打,否则换方法

从x节点向上走到根节点,把所有经过的节点标记。

从y节点向上走,第一次遇到被标记过的节点的时候,该节点为所求点。

每次询问,最坏复杂度为O(n)

解法二:树上倍增法

不温不火,很适合考场的时候打啊…

首先理解倍增算法。类似于动态规划思想

复杂度O((n+m)log n)

倍增预处理

设F[x,k]表示x的2k辈祖先。也就是,x向上跳2k所到达的节点。如果跳到了一个压根不存在的节点,我们让他的值为0。

显然根据定义,可以得出F[x,0]就是x的父节点。

为了不少跳,求出k的范围应当属于[1,logn]。在c++中,应写为

t=(int)log(n)/log(2)+1

接下来就有转移方程:

F[x,k]=F[F[x,k-1],k-1] ,因为 2k-1+2k-1=2* 2k-1=2k

预计需要的时间复杂度O(n logn)

void bfs(){
    q.push(1);
    d[1]=1;
    while(q.size()){
        int x=q.front();q.pop();
        for(int i=Head[x];i;i=Next[i]){
            int y=End[i];
            if(d[y]) continue;
            d[y]=d[x]+1;
            f[y][0]=x;
            for(int j=1;j<=t;j++){
                f[y][j]=f[f[y][j-1]][j-1];
            }
            q.push(y);
        }
    }
}

跳跃式处理LCA 

由上面的预处理,我们得到了已经初始化的D(深度)数组和F(倍增)数组

我们假定D[x]>=D[y]  (否则你就交换一下嘛)

1.让x往上跳,先跳到和y一样高再说

  怎么实现呢?枚举。枚举上跳步数。枚举jump=2logn一直枚举到20。为什么要倒着呢?因为要尽可能跳的次数少一点降低复杂度。

  检查即将上跳到的节点是否比y要低一点。如果是就完成上跳,即让x=F[x,jump]

2.如果发现仅仅完成第一步就有了x==y(x跳到y脸上去了),说明y本来就是x的祖先,那么LCA就等于y。

3.让x和y一起往上跳,保持两者深度相同但不相汇。依然按照步骤一一样去枚举jump。只要F[x,jump]不等于F[y,jump],就赋值起跳。

4.经过了步骤3的磨砺,x和y一定就差最后一步就可以相汇了。那么两者任意一个节点的父节点就必然是LCA(x,y)。所以此时LCA(x,y)=F[x,0]

int lca(int x,int y){
    if(d[x]>d[y]) swap(x,y);
//请注意!在这段代码中,d[x]<=d[y]!与上述讲解截然相反!
for(int i=t;i>=0;i--){ if(d[f[y][i]]>=d[x]) y=f[y][i]; if(x==y) return x; } for(int i=t;i>=0;i--){ if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; } return f[x][0]; }

 用途

求树从x到y结点最短路径上所有节点的值之和

其实这是写这篇博文的目的…

怎么办呢?其实也只是个性质。即从根节点出发做一次单源最短路(或者直接Dfs)。那么Distance ( x , y ) = Distence ( x ) + Distence ( y ) - 2 * Distence ( Lca )

所以只需要O(n)求最短路径,再求个LCA即可。

posted @ 2019-11-08 18:57  L1ngYi  阅读(312)  评论(0编辑  收藏  举报