lca判断祖孙结点。

本文介绍了线段树求最近公共祖先(LCA)的算法,详细阐述了预处理每个节点深度和向上跳跃的过程,并提供了洛谷板子题的代码示例。通过倍增思想和二进制优化,能够在O(logn)时间内求解树上两点的LCA。文章适合对数据结构和算法有一定基础的读者学习。
摘要由CSDN通过智能技术生成

lca算法

今天是个好日子 2022 - 7-15(萌新,如果有错误的地方还请指正QAQ

lca是一种可以快速在线求出一个树上的两个点的最近公共祖先,其思路是先预处理出来每个点的深度,然后用倍增的思想,判断每个节点向上跳2的i次方的点会到哪个位置(类似于二进制优化的思想,这样可以拼凑出任何一个数)。前方的操作预处理过后就开始查询了

预处理每一个点的深度,和求出每一个点向上跳的距离所对应的点。

注意一个点,在求得深度时,我们会在根节点的上方多设置一个节点0,这样的目的是为了在向上跳的时候,因为向上跳的时候枚举的是从大到小的2的n次方可以向上跳的高度,而如果这个跳跃的高度太大的话,就会到一个初始值0,而两个点同时向上跳时,我们使循环结束的条件是:两个点再向上跳一个点就会到他们的最近公共祖先,所以我们需要 设置一个哨兵来在逻辑上满足这个条件。在求深度的时候使用的是bfs

  1. 首先将所有点的深度置为正无穷(0x3f3f3f3f),并设置depth[0] = 0 (哨兵,防止跳跃的太大而出界) 在将depth[root] 置为1.

  2. 首先先将根结点加入到队列当中,然后走通常的bfs格式。

  3. 拿出队首t ,枚举 t 的所有边,如果说相连的那条边所对应的点 J 深度小于当前点的深度+1,就将j的深度置为depth[t]+1,并将该点加入到队列当中,然后设置他的向上跳2^0次所到达的地方为t 即 f【j】【0】 = t ; 然后枚举 j 可以向上跳的所有点。因为是从根向下遍历,所以可以直接用上边已经求得的数据来更新当前的j点。并且有一个性质为 f 【a】【b】 = f【f【a】【b-1】】【b-1】,我们直接在逻辑上理解这个等式。当前点向上跳2^b 次方次 就相当于当前点 先向上跳 2^b-1次方到的点 now , 再从now向上跳2^b-1次方到达的点。这个过程就是向上跳的过程。由于是b-1,所以b枚举时需要从1开始枚举。这个过程完之后,处理完所有点,预处理就完成了


  1. 首先先将两个点移动到同一层:先将深度深的点A一直向上跳,直到与B点的深度相同,然后判断当前点是否是同一个,如果是同一个的话,点B就就是两个节点的公共祖先。

  2. 在判断,如果当前两个点在同一层之后并不在同一个点,则同时跳A和B,直到两个点还有一个点就到最近公共节点的时候停止,这时候只需要返回他们的父结点即可。

下面就是代码和题的链接

链接是洛谷的的题lca板子题

下面是代码块

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<queue>
​
using namespace std;
const int N = 500010, M = 1000010;
int h[N],e[M],ne[M],idx;
int depth[N];
int fa[N][21];
int n , m ;
void add(int a, int b )
{
    e[idx] = b, ne[idx] = h[a] , h[a] = idx ++ ;
}
​
void bfs(int root)
{
    memset(depth,0x3f,sizeof depth);
    depth[0] = 0 ;//哨兵
    depth[root] = 1;
    queue<int> q;
    q.push(root);
    while(q.size())
    {
        auto t = q.front();
        q.pop();
​
        int len = depth[t] + 1;
        for(int i = h[t] ; ~i ; i = ne[i])
        {
            int j = e[i];
            if(depth[j] > len)
            {
                depth[j] = len;
                q.push(j);
                fa[j][0] = t;
                for(int b = 1; b <= 19 ; b++)
                {
                    fa[j][b] = fa[fa[j][b-1]][b-1];
                }
            }
        }
    }
}
​
int find(int a, int b)
{
    if(depth[a] < depth[b])swap(a,b);
​
    for(int i = 19; i >= 0 ; i --)
    if(depth[fa[a][i]] >= depth[b])a = fa[a][i];//跳到同一层。
    if(a == b)return b ;//如果其中一个是另一个的祖先直接返回。
    for(int i = 19 ; i >= 0 ; i --)//两个同时跳。
    {
        if(fa[a][i] != fa[b][i])
        {
            a = fa[a][i];
            b = fa[b][i];
        }
    }
    return fa[a][0];//返回当前结点的父结点。
}
​
int main()
{
    int root;
    memset(h,-1,sizeof h);
    cin >> n >> m >> root;
    int a, b ;
    for(int i = 1; i <= n-1 ; i ++)
    {
        cin >> a >> b ;
        add(a,b);
        add(b,a);
    }
    bfs(root);
    while(m--)
    {
        int l ,r ;
        cin >> l >> r;
        cout << find(l,r)<<endl;
    }
    return 0 ;
}
​

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值