最近公共祖先(LCA)之树上倍增法

本文介绍了一种高效求解有根树中两个节点最近公共祖先(LCA)问题的算法——树上倍增。该算法通过预处理每个节点的2^k级父节点来实现对LCA的快速查找,时间复杂度为log(n)。文中详细解释了算法原理,并提供了完整的C++代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近公共祖先

对于有根树T的两个结点u,v,最近公共祖先LCA(T,u,v)表示一个节点x,满足x是u,v的祖先并且x的深度尽可能大
也可以把T理解为一个无向无环图,而LCA(T,u,v)即u到v的最短路上森度最小的点

所以,LCA主要就是求解当两个点仅有唯一一条确定的最短路径时的路径信息的问题。

树上倍增

如果我们直接一个一个结点遍历去寻找这样的祖先,复杂度是O(n)的。而在算法竞赛中,经常会有个数较多的询问。比如50000个点,50000个询问,这时直接遍历的方法耗时较多。
下面是一种被称作树上倍增的算法,实际上它的思想类似于二分枚举法,时间复杂度是log(n)的。

终究都要走到一起(树根),可以考虑第一个不同祖先,这是倍增法的思想

所谓倍增,就是两倍,即从一个节点逐渐往上找k级父亲(所跨层数为2k20即为通常所说的父结点)

具体操作:先使两个结点深度相同(在同一层),然后将k从大到小进行枚举。之前要对2k结点作预处理。

往往题目中不光要求LCA,还询问路径上的一些信息(如权值之和或者权值最大),因此还要另外记录。

参考代码

存储结构

const int maxn=100000;//节点数
vector<Edge> edges;//边的具体信息
vector<int> G[maxn];//边的编号

const int maxlog=30;
int grand[maxn][maxlog];
int gdis[maxn][maxlog];
int gmax[maxn][maxlog];
int depth[maxn];
int n;//结点数
int s;//倍增最大步数
int root;//根节点

预处理

void addEdge(int u,int v,int w){//vector邻接表
    edges.push_back(Edge(u,v,w));
    edges.push_back(Edge(v,u,w));
    int size=edges.size();
    G[u].push_back(size-2);
    G[v].push_back(size-1);
}

void dfs(int x)//预处理
{
    for(int i=1;i<=s;++i){//k级父亲
        grand[x][i]=grand[grand[x][i-1]][i-1];
        gdis[x][i]=gdis[x][i-1]+gdis[grand[x][i-1]][i-1];
        gmax[x][i]=max(gmax[x][i-1],gmax[grand[x][i-1]][i-1]);
        if(!grand[x][i]) break;
    }
    for(int i=0;i<G[x].size();i++){
        Edge & e=edges[G[x][i]];
        if(e.to!=grand[x][0]){
            depth[e.to]=depth[x]+1;
            grand[e.to][0]=x;
            gdis[e.to][0]=e.dist;
            gmax[e.to][0]=e.dist;
            dfs(e.to);
        }
    }
}

void init()//初始化
{
    s=floor(log(n+0.0)/log(2.0));
    depth[0]=-1;
    memset(grand,0,sizeof(grand));
    memset(gdis,0,sizeof(gdis));
    memset(gmax,0,sizeof(gmax));
    root=1;
    dfs(root);//以1为根结点建树
}

核心代码

int lca(int a,int b,int &maxx,int &ans)//maxx,ans:最大值,路径权值和
{
    if(depth[a]>depth[b]) swap(a,b);
    ans=0;//路径权值和
    maxx=gmax[b][0];
    //for(int i=s;i>=0;i--)
    //    if(depth[a]<depth[b]&&depth[grand[b][i]]>=depth[a])
    //        ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i];

    int dre=depth[b]-depth[a];
    for(int i=s;i>=0;--i){
        if(dre&(1<<i)) ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i];
    }
    if(a==b) return a;
    for(int i=s;i>=0;i--)
        if(grand[a][i]!=grand[b][i]){
            ans+=gdis[a][i],ans+=gdis[b][i];
            maxx=max(maxx,gmax[a][i]),maxx=max(maxx,gmax[b][i]);
            a=grand[a][i],b=grand[b][i];
        }

    ans+=gdis[a][0];
    ans+=gdis[b][0];
    maxx=max(maxx,gmax[a][0]);
    maxx=max(maxx,gmax[b][0]);
    return grand[a][0];
}

总体示例(vector)

#include<bits/stdc++.h>
using namespace std;

struct Edge{
    int from,to,dist;
    Edge(int u,int v,int w):from(u),to(v),dist(w){
    }
};

const int maxn=100000;//节点数
vector<Edge> edges;//边的具体信息
vector<int> G[maxn];//边的编号

void addEdge(int u,int v,int w){//vector邻接表
    edges.push_back(Edge(u,v,w));
    edges.push_back(Edge(v,u,w));
    int size=edges.size();
    G[u].push_back(size-2);
    G[v].push_back(size-1);
}

const int maxlog=30;
int grand[maxn][maxlog];
int gdis[maxn][maxlog];
int gmax[maxn][maxlog];
int depth[maxn];
int n;//结点数
int s;//倍增最大步数
int root;//根节点

void dfs(int x)//预处理
{
    for(int i=1;i<=s;++i){
        grand[x][i]=grand[grand[x][i-1]][i-1];
        gdis[x][i]=gdis[x][i-1]+gdis[grand[x][i-1]][i-1];
        gmax[x][i]=max(gmax[x][i-1],gmax[grand[x][i-1]][i-1]);
        if(!grand[x][i]) break;
    }
    for(int i=0;i<G[x].size();i++){
        Edge & e=edges[G[x][i]];
        if(e.to!=grand[x][0]){
            depth[e.to]=depth[x]+1;
            grand[e.to][0]=x;
            gdis[e.to][0]=e.dist;
            gmax[e.to][0]=e.dist;
            dfs(e.to);
        }
    }
}

void init()
{
    s=floor(log(n+0.0)/log(2.0));
    depth[0]=-1;
    memset(grand,0,sizeof(grand));
    memset(gdis,0,sizeof(gdis));
    memset(gmax,0,sizeof(gmax));
    root=1;
    dfs(root);//以1为根结点建树
}

int lca(int a,int b,int &maxx,int &ans)//maxx,ans:最大值,路径权值和
{
    if(depth[a]>depth[b]) swap(a,b);
    ans=0;//路径权值和
    maxx=gmax[b][0];
    //for(int i=s;i>=0;i--)
    //    if(depth[a]<depth[b]&&depth[grand[b][i]]>=depth[a])
    //        ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i];

    int dre=depth[b]-depth[a];
    for(int i=s;i>=0;--i){
        if(dre&(1<<i)) ans+=gdis[b][i],maxx=max(maxx,gmax[b][i]),b=grand[b][i];
    }
    if(a==b) return a;
    for(int i=s;i>=0;i--)
        if(grand[a][i]!=grand[b][i]){
            ans+=gdis[a][i],ans+=gdis[b][i];
            maxx=max(maxx,gmax[a][i]),maxx=max(maxx,gmax[b][i]);
            a=grand[a][i],b=grand[b][i];
        }

    ans+=gdis[a][0];
    ans+=gdis[b][0];
    maxx=max(maxx,gmax[a][0]);
    maxx=max(maxx,gmax[b][0]);
    return grand[a][0];
}



int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)
    {
        int maxx,sum;
        edges.clear();
        for(int i=0;i<maxn;++i){
            G[i].clear();
        }

        int query,u,v,w;
        cin>>n>>query;
        for(int i=1;i<=n-1;++i){
            cin>>u>>v>>w;
            addEdge(u,v,w);
        }
        init();
        for(int i=1;i<=query;++i){
            cin>>u>>v;
            lca(u,v,maxx,sum);
            cout<<lca(u,v,maxx,sum)<<endl;
            cout<<maxx<<endl;
            cout<<sum<<endl;
        }
    }
    return 0;
}

优化(链式向前星,数据量较大时可以优化常数)

//链式向前星也是存图的一种方式,用数组模拟邻接表,在点数较多时较vector优
int head[2*maxn];

void addEdge(int u,int v){//关键部分
    edges[cnt].from=v;
    edges[cnt].to=head[u];
    head[u]=cnt++;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值