zoukankan      html  css  js  c++  java
  • 树上倍增求LCA详解

    LCA(least common ancestors)最近公共祖先

    指的就是对于一棵有根树,若结点z既是x的祖先,也是y的祖先,那么z就是结点x和y的最近公共祖先。

    定义到此。

    那么怎么求LCA

    对于朴素思想,就是我要一步一步往上爬,一步一步走。先把结点x和y整到同一深度,然后再一次一个深度的往上查,直到祖先一样才break(明显是个while)

    但是,一步一步实在是太慢了,所以不能脚踏实地地走

    那么,考虑跳着走,

    跳着走的条件就是要满足一步步数尽可能多并且不跳过了。当然你跳过了在一步一步往下找也行啊QWQ

    于是,在满足这两个条件的情况下,我们有了LCA算法:

    一步跳2的n次方。

    对于每一步跳跃,我们要预处理一个二维数组f(father)f[x][i],表示对于当前的x结点,往上跳2^i个祖先,特别的,像数学中的,f[x][0]就是x的一代祖先。于是我们要用一个dfs预处理来解决这个f数组。后面我们会提到;

    处理完f数组,那就很简单了。

    输入x和y,表示要求结点x和结点y的LCA,那么我们开始求LCA:

    对于x和y在不同高度,我们先把他们拉到一个高度,同样不能一步一步走,也要用到f数组。

    这里我们要提前了解到一个定理:对于任意一个非零整数,我们都可以将他用2的次幂表示出来。也就是such as  : 11=2^3+2^1+2^0。这倒也不用证明,就像每个数都可以用二进制表示的原理。

    接着讲:在把x和y弄到同一高度时,我们要先做的是设x的深度dep要比y的深度dep要大,如果dep[y]>dep[x],那么把他们交换。原因是如果不交换,那么我们需要判断两种情况,用两个if以及几乎相同的代码(乱七八糟还占内存),对于非常懒的我们,自然不会这么干。

    然后用一个判断    if(dep[f[x][i]]>=dep[y])x=f[x][i];(此时x深度大于y),在这里用外层for循环来枚举i,但是i一定要从大到小!为什么?

    安利一个大家都听烂了故事:一个玻璃瓶,装了几块石头,满到瓶口。满了吗?没有。又装了一些沙子,满到瓶口。满了吗?没有。最后又装满了水,满到瓶口。终于满了。

    那么,类比于x和y的深度的距离,这个瓶子的容积也是同样道理。从2的尽可能大的次幂去找,一旦能找到能接近他们的i,就更新dep[x],直到相等。类似于无限逼近,最后值相等的过程。如果i相等了,就说明他们的深度已经在同一层了。

    与倍增到同一深度的过程相比,倍增找公共祖先也是类似的,但是有一点不同,就是你不知道什么时候找到他们的公共祖先,因此就没有查找的上限,那么就让他们尽可能接近。因此条件改成了这个:

    if(f[x][i]!=f[y][i])
            {
                x=f[x][i];y=f[y][i];
            }

    在执行完这个语句后,我们成功让x和y变成了某一节点z的左右儿子!

    因为左右儿子是最接近但是又不相等的。

    那么我们随便取其中一个找爸爸,就找到了LCA。

     

    AC代码:

    有关链式存图不懂的的点这里。

    #include<cstdio>
    #include<iostream>
    using namespace std;
    int n,m,s,num=0,head[1000001],dep[1000001],f[1000001][23];
    int a1,a2;
    struct edg{
        int next,to;
    }edge[1000001];
    void edge_add(int u,int v)//链式前向星存图 
    {
        num++;
        edge[num].next=head[u];edge[num].to=v;head[u]=num;
        edge[++num].next=head[v];edge[num].to=u;head[v]=num;
    }
    void dfs(int u,int father)//对应深搜预处理f数组 
    {
        dep[u]=dep[father]+1;
        for(int i=1;(1<<i)<=dep[u];i++)
        {
            f[u][i]=f[f[u][i-1]][i-1];
        }
        for(int i=head[u];i;i=edge[i].next)
        {
            int v=edge[i].to;
            if(v==father)continue;//双向图需要判断是不是父亲节点 
            f[v][0]=u;
            dfs(v,u);
        }
    }
    int lca(int x,int y)
    {
        if(dep[x]<dep[y])swap(x,y);
        for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层 
        {
            if(dep[f[x][i]]>=dep[y])x=f[x][i];
            if(x==y)return x;
        }
        for(int i=20;i>=0;i--)//从大到小枚举 
        {
            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",&a1);scanf("%d",&a2);
            edge_add(a1,a2);//链式存边 
        }
        dfs(s,0);
        for(int i=1;i<=m;i++)
        {
            scanf("%d %d",&a1,&a2);
            printf("%d
    ",lca(a1,a2));//求两个节点的LCA 
        }
    } 

    完结撒花✿ヽ(°▽°)ノ✿有疑问或者有可以改进讲解的地方可以指出。

  • 相关阅读:
    [转]Extundelete--数据恢复软件
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
  • 原文地址:https://www.cnblogs.com/lbssxz/p/11114819.html
Copyright © 2011-2022 走看看