( 数据结构专题 )【 倍增LCA 】

( 数据结构专题 )【 倍增LCA 】

推荐视频:https://www.bilibili.com/video/av95465637?from=search&seid=12848805369174984057

指的就是对于一棵有根树,若结点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。

啊。。。。。。喘口气

模板题目:https://www.luogu.com.cn/problem/P3379

n个点,n-1条边的树,root为根,m次询问找(a,b)的lca。

代码:

#include <bits/stdc++.h>

using namespace std;

struct node {
    int to,nxt;
}e[2000005];

int head[500005],cnt,n,m,root;
int f[500005][22];
int dep[500005];

void addage( int u, int v )
{
    e[cnt].to = v;
    e[cnt].nxt = head[u];
    head[u] = cnt++;
}

void dfs( int u, int fa ) // 对应深搜预处理f数组
{
    dep[u] = dep[fa] + 1;
    for ( int i=1; (1<<i)<=dep[u]; i++ ) {
        f[u][i] = f[f[u][i-1]][i-1];  // 定义,往上走8步可以分为,先走4步再走4步
    }
    for ( int i=head[u]; i!=-1; i=e[i].nxt ) {
        int v = e[i].to;
        if ( v==fa ) 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-- ) {
        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()
{
    memset(head,-1,sizeof(head));cnt=0;
    cin >> n >> m >> root;
    for ( int i=0; i<n-1; i++ ) {
        int u,v;scanf("%d %d",&u,&v);
        addage(u,v); addage(v,u);
    }
    dfs(root,root);
    for ( int i=0; i<m; i++ ) {
        int a,b;scanf("%d %d",&a,&b);
        printf("%d\n",lca(a,b));
    }

    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值