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

(文章目录)


前言

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


题目大意

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

对于 100%100\% 的数据,1N,M5×1051 \leq N,M\leq 5\times 10^51x,y,a,bN1 \leq x, y,a ,b \leq N不保证 aba \neq b


思路

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

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

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

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

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


代码

#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的时间复杂度。