倍增法求LCA

LCA就是最近公共祖先的意思,比如这个图
在这里插入图片描述
这个图中10 和 9 的最近公共祖先就是7
这个图中4 和10的最近公共祖先就是1
那么我们到底怎么来求最近公共祖先呢?

  • 有两种方法来求最近公共祖先,1是倍增法,2是tarjan,这是一种离线算法,由于本人比较弱,所以只会第一种算法(主要是害怕弄混了,因为图的强连通分量里面会学另一个tarjan算法,还是自己比较弱)
  • 倍增法里面有一个很重要的数组f[x,k],表示x往上跳 2 k 2^{k} 2k步所能够到达的点,拿上面的图举例子
  • f[10,0] = 8,f[10,1] = 7,f[10,2] = 5···,再举一个例子f[9,0] = 7,f[9,1] = 6,f[9,2] = 1
  • 这里一个点往上跳 2 k 2^k 2k步不太好求,那么可以先跳 2 k − 1 2^{k - 1} 2k1,然后再跳 2 k − 1 2^{k - 1} 2k1,这样就相当于我们跳了 2 k 2^{k} 2k
  • 假设t 是我们当前点跳了 2 k − 1 2^{k - 1} 2k1,那么t = f[x,k - 1];f[x,k] = f[t,k - 1],也就是f[x][k] = f[f[x][k - 1]][k - 1];
  • 还有我们要算出根节点到每个点的距离,也就是每个点的深度,此时根节点的深度默认为1,depth[root] = 1;
  • 可以通过bfs或者dfs来预处理f数组
const int N = 4e4 + 10,M = N * 2;

int h[N],e[M],ne[M],idx;

int n,m;
int root;
int depth[N];
int f[N][19];

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}

void dfs(int u)
{
    for(int i = h[u];~i;i = ne[i])
    {
        int j = e[i];
        if(!depth[j]){
            depth[j] = depth[u] + 1;
            
            /* 
                下面是重点,其实下面这个16是通过求log(n),算出来的,也就是我们最多
                会跳2^15步数,具体题目具体分析一般写大一点肯定没问题,别超过20;
            */
            f[j][0] = u;
            
            for(int k = 1;k < 16;k ++)
            {
                int t = f[j][k - 1];
                f[j][k] = f[t][k - 1];
            }
            dfs(j);
        }
    }
}
  • 那么f数组预处理出来了,那么下面就是查找最近公共祖先的技巧了
  • 首先是把让x跳到和y一层(默认x比y的深度深,如果x比较浅的话,swap(x,y))
  • 跳到和y同一层,假设我们x需要跳z步数才能和y一层,由于任何一个数都可以被拆分成很多2的不同次幂加起来,所以我们就从2的最高次幂(也就是15)开始往上跳,如果跳到一个点p,depth[p] >= depth[y],那么我们还是可以继续往上跳···
  • 假设此时我们已经跳到了和y同一层,那么我们让x,y往上跳相同的步数,让他们跳到其公共祖先的下面一个节点,注意此时的这个节点可能相同,也可能不同,但是这个节点再往上跳一步肯定是相同的,比如3和9的最近公共祖先是1,而我们只会跳到2号节点和5号节点,只需要这个节点再往上跳一下就到达了最近公共祖先节点
  • 至于为什么不直接跳到最近公共祖先节点呢?
  • 我画一个图
  • 在这里插入图片描述
  • 假设3和4往上跳,那么他们最先跳到1号节点,而1号节点并不是它们的最近公共祖先,2号节点才是,所以我们跳到最近公共祖先的下一个节点
int lca(int a,int b)
{
// a的深度比b浅
    if(depth[a] < depth[b]){
        swap(a,b);
    }
// 把a跳到和b同一层
    for(int i = 15;i >= 0;i --)
    {
        if(depth[f[a][i]] >= depth[b]){
            a = f[a][i];
        }
    }
    if(a == b) return a;
    
    //  下面是a和b同时往上跳,注意此时a和b是在同一层的,同一层,同一层,三遍~~~~~~
    for(int i = 15;i >= 0;i --)
    {
        if(f[a][i] != f[b][i])
        {
            a = f[a][i],b = f[b][i];    
        }
    }
    return f[a][0];
    
}

哪里不对希望大佬批评指正,或者哪里不理解可以私信问我

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Tarjan算法是一种用于解决最近公共祖先(LCA)问题的离线算法。离线算法指的是在读取所有查询之后一次性计算所有查询的答案,而不是每读取一个查询就计算一次。\[1\] 在Tarjan算法中,需要使用并查集来实现。并查集是一种数据结构,用于维护元素之间的集合关系。下面是一个并查集的模板代码: ```cpp int fa\[100000\]; void reset(){ for (int i=1;i<=100000;i++){ fa\[i\]=i; } } int getfa(int x){ return fa\[x\]==x?x:getfa(fa\[x\]); } void merge(int x,int y){ fa\[getfa(y)\]=getfa(x); } ``` 在Tarjan算法的伪代码中,首先标记当前节点为已访问状态。然后遍历当前节点的子节点,递归调用Tarjan函数并合并子节点。接下来,遍历与当前节点有查询关系的节点,如果该节点已经访问过,则输出当前节点和该节点的LCA(通过并查集的查找函数getfa获取)。\[3\] 以上是关于Tarjan算法求解LCA的相关内容。 #### 引用[.reference_title] - *1* [Tarjan 算法解决 LCA 问题](https://blog.csdn.net/chengqiuming/article/details/126878817)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [详解使用 Tarjan 求 LCA 问题(图解)](https://blog.csdn.net/weixin_34315485/article/details/93801193)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值