P3379 【模板】最近公共祖先(LCA)


前言

最近公共祖先是一种求树上两点最近的共同祖先的算法。


题目大意

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 5 × 1 0 5 1 \leq N,M\leq 5\times 10^5 1N,M5×105 1 ≤ x , y , a , b ≤ N 1 \leq x, y,a ,b \leq N 1x,y,a,bN不保证 a ≠ b a \neq b a=b


思路

首先暴力做法很好想,就是先把两点的深度弄成一样的,让较深的点一步一步往上爬,之后再让两个点同时往上爬,时间复杂度 O ( n ) \mathcal{O}(n) O(n),这是不行的。

倍增法优化。设 f a [ u ] [ i ] fa[u][i] fa[u][i] u u u 的第 2 i 2^i 2i 个祖先。之后就考虑直接跳那么多层变成 O ( log ⁡ n ) \mathcal{O}(\log{n}) O(logn) 的时间复杂度查询一次。

预处理就不说了,很简单。一会儿写注释里。

之后先考虑调到同一深度,两点 u , v u,v u,v,不妨设 v v v 的深度大于 u u u。那么 v v v 需要跳 d e p [ v ] − d e p [ u ] dep[v]-dep[u] dep[v]dep[u] 层。那么,如果对这个数进行二进制分解,如果从右往左第 i i i 位是 1 1 1,那么就可以往上跳 2 i − 1 2^{i-1} 2i1 层。这样深度就一样了。

之后枚举每一个 i i i,也就是指数,从大到小。如果祖先不一样了,就说明两个点肯定到那里都不相聚,就往上跳。最后相遇了为止。


代码

#include <iostream>
using namespace std;
typedef long long ll;
const ll MAXN=1e6+5;
struct edge{
    ll to,nxt;
}e[MAXN];
ll head[MAXN],tot,n,m,s,fa[MAXN][37],dep[MAXN];
void add(ll u,ll v){
    e[++tot].nxt=head[u];
    e[tot].to=v;
    head[u]=tot;
}
void dfs(ll u,ll f){
    fa[u][0]=f;//往上一级是父亲
    dep[u]=dep[f]+1;
    for (int i = 1; i <=31 ; ++i) {
        fa[u][i]=fa[fa[u][i-1]][i-1];//2^i=2^{i-1}+2^{i-1}
    }
    for (ll i = head[u]; i ; i=e[i].nxt) {
        ll v=e[i].to;
        if(v!=f){
            dfs(v,u);
        }
    }
}
ll lca(ll u,ll v){
    if(dep[u]>dep[v]){
        swap(u,v);//v的深度大于u
    }
    ll y=dep[v]-dep[u];
    for (int i = 0; y ; ++i) {
        if(y&1){
            v=fa[v][i];//二进制分解如果为1就往上跳
        }
        y>>=1;
    }
    if(u==v){
        return u;//如果都碰上了就直接返回
    }
    for (int i = 30; i>=0; --i) {
        if(fa[u][i]!=fa[v][i]){
            u=fa[u][i];//祖先不同就都往上跳
            v=fa[v][i];//同上
        }
    }
    return fa[u][0];//跳到最低位,再往上就一样了。
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&s);
    for (int i = 1; i <n ; ++i) {
        ll u,v;
        scanf("%lld%lld",&u,&v);
        add(u,v);
        add(v,u);
    }
    dfs(s,0);
    for (int i = 1; i <=m ; ++i) {
        ll u,v;
        scanf("%lld%lld",&u,&v);
        printf("%lld\n", lca(u,v));
    }
    return 0;
}

总结

学习了通过倍增的方法来大幅度优化暴力LCA的时间复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值