最近公共祖先(LCA)

最近公共祖先(LCA

对于任意一棵树:

一个节点的父节点、父节点的父结点、父节点的父节点的父节点 · · · · · · (此处省略 114514 个套娃) ,以及这个节点本身,都叫做这个节点的 祖先 。两个及以上节点共同的祖先,叫做这两个及以上节点的 公共祖先 。其中深度最深的那一个公共祖先叫做这两个及以上节点的 最近公共祖先(LCA

性质

  • LCA({x})=x

  • u 是 v 的祖先,当且仅当 LCA({u,v})=u

  • 如果 u 不为 v 的祖先并且 v 不为 u 的祖先,那么 u 和 v 分别处于 LCA({u,v}) 的两棵不同子树中;

  • 前序遍历中,LCA(S) 出现在所有 S 中元素之前,后序遍历中 LCA(S) 则出现在所有 S 中元素之后;

  • 两点集并的最近公共祖先为两点集分别的最近公共祖先的最近公共祖先,即 LCA(AB)=LCA(LCA(A),LCA(B))

  • 两点的最近公共祖先必定处在树上两点间的最短路上;

  • d(u,v)=h(u)+h(v)2×h(LCA({u,v})),其中 d(u,v) 是树上两点间的距离,h(u) 代表某点到树根的距离。

一、朴素法(向上查找法)求解LCA

这个方法是最简单的求解最近公共祖先的方法。

具体思路就是标记一个节点的所有祖先,再扫描另一个节点的所有祖先,第一个被扫到且被标记的祖先即为这两个节点的最近公共祖先。

#include <iostream>
#include <cstring>
using namespace std;
int n,m,fa[10010],b[10010];
int LCA(int x,int y){
    int z=x;
    while(z){
        b[z]=1;
        z=fa[z];
    }
    z=y;
    while(z){
        if(b[z]) return z;
        z=fa[z];
    }
} 
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>fa[i];
    }
    for(int i=1;i<=m;++i){
        int x,y;
        cin>>x>>y;
        memset(b,0,sizeof(b));
        cout<<LCA(x,y)<<endl;
    }
    return 0;
}

单次时间复杂度最坏 O(N)

二、树上倍增法求解LCA

树上倍增法是一种非常重要的算法,有着非常广泛的应用。

fi,ji 点的向上跳 2j 次的祖先,那么就有以下递推式:

1in,1jlog2n,fi,j=ffi,j1,j1

f 数组可以通过 dfs 预处理出来。

那么 fi,0 就是 i 的父亲节点。

在查询的时候,通过倍增确定 xy 两点深度最浅的非公共祖先,其父节点就是 LCA。

    #include <bits/stdc++.h>
    #define ll long long
    #define ull unsigned long long
    using namespace std;
    int f[500010][100],n,Root,fa[500010],m,dep[500010];
    vector<int>v[500010]; 
    void dfs(int x,int fa) {
        dep[x]=dep[fa]+1;
        f[x][0]=fa;
        for(int i=1;i<=;++i){
            f[x][i] = f[f[x][i-1]][i-1];//递推式 
        }
        for(int i=0;i<v[x].size();++i){
            if(v[x][i] != fa) dfs(v[x][i],x);
        }
    }
    int lca(int x, int y) {
        if(dep[x]<dep[y]) swap (x, y);
        for(int i = 19; i >= 0; --i) {
            if(dep[f[x][i]] >= dep[y]) x = f[x][i];
        }
        if(x == y) return x;
        for(int i = 19; i >= 0; --i) {
            if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
        }
        return f[x][0];
    }
    int main(){
        cin>>n>>m>>Root;
        for(int i=1;i<n;++i){
            int u,w;
            scanf("%d%d",&u,&w);
            v[u].push_back(w);
            v[w].push_back(u);
        }
        dfs(Root,0);
        for(int i=1; i <= m; ++i) {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%d\n",lca(x,y));
        }
        return 0;
    }

三、Tarjan 求 LCA

鸽子中……

posted @   cannotmath  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示