洛谷 3379 最近公共祖先(LCA 倍增)

洛谷 3379 最近公共祖先(LCA 倍增)

题意分析

裸的板子题,但是注意这题n上限50w,我用的边表,所以要开到100w才能过,一开始re了两发,发现这个问题了。

代码总览

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define nmax 1000100
#define demen 21
using namespace std;
int fa[nmax][demen],dis[nmax],head[nmax],dep[nmax];
int n,m,tot = 0;
struct node{
    int to;
    int next;
    int w;
}edge[nmax];
void add(int u, int v){
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}

void dfs(int rt,int f){
    fa[rt][0] = f;
    for(int i = 1;i<=20;++i){
        fa[rt][i] = fa[fa[rt][i-1]][i-1];
    }
    for(int i = head[rt];i!=-1;i = edge[i].next){
        int nxt = edge[i].to;
        if(nxt != f){
            dep[nxt] = dep[rt] + 1;
            dfs(nxt,rt);
        }
    }
}
int lca(int x, int y){
    int X = x,Y=y;
    if(dep[x] < dep[y]) swap(x,y);
    int dre = dep[x] - dep[y];
    for(int i = 20;i>=0;--i){
        if((1<<i) & dre)
            x = fa[x][i];
    }
    if(x == y) return(x);
    for(int i = 20;i>=0;--i){
        if(fa[x][i] != fa[y][i]){
            x = fa[x][i],y = fa[y][i];
        }
    }
    return(fa[x][0]);
}
void init(){
    memset(fa,0,sizeof fa);
    memset(head,-1,sizeof head);
    memset(dep,0,sizeof dep);
    tot = 0;
}
int main()
{
    init();
    int n,m,k,u,v,w,root = 0;
    scanf("%d %d %d",&n,&k,&root);
    for(int i = 0;i<n-1;++i){
        scanf("%d %d",&u,&v);
        add(u,v);
        add(v,u);
    }
    dep[root] = 1;
    dfs(root,0);
    int ans = 0;
    for(int i = 0;i<k;++i){
        scanf("%d %d",&u,&v);
        printf("%d\n",lca(u,v));
    }

    return 0;
}
### 算法原理 倍增法求解最近公共祖先LCA)的核心在于利用二进制的特性进行高效的祖先查找。首先,给定一棵树,通过深度优先搜索(DFS)从根节点开始遍历,计算每个节点的深度,并初始化关键数组`fa[i][j]`,该数组表示从`i`节点往根节点出发的第$2^j$个祖先节点,其中`fa[i][0]`即`i`节点的父节点。 在查询两个节点`u`和`v`的LCA时,先让深度较深的节点`u`通过`fa`数组进行跳跃,使得`u`和`v`处于相同的深度。这是因为任意一个大于等于0的数都可以由$1, 2, 4, 8, 16, 32, \cdots$这些数凑出来。之后,如果`u`和`v`相等,那么这个节点就是最近公共祖先;若不相等,则让`u`和`v`同时通过`fa`数组向上跳跃,直到它们的某一级祖先不同,最终返回`fa[u][0]`,即`u`的父节点,此节点即为最近公共祖先 [^1][^4]。 另外,两点集并的最近公共祖先为两点集分别的最近公共祖先最近公共祖先,即$LCA(A \cup B) = LCA(LCA(A), LCA(B))$,利用这个性质,可以求解任意多节点之间的最近公共祖先 [^2][^3]。 ### 代码实现 以下是使用倍增法求解最近公共祖先LCA)的核心代码: ```python # lcaMultiply 函数返回u和v的父节点 def lcaMultiply(u, v): # 倍增 if depth[u] < depth[v]: u, v = v, u # 让u指向深度最大的节点 for i in range(19, -1, -1): # 向上找u的祖先,通过数学可以推出来,最终u和v的深度一定会相等,因为两者深度差为一个大于等于0的数,如果等于0什么都不做,对于大于0的数,一定可以通过1,2,4,8,16,32,...,凑出来 if depth[fa[u][i]] >= depth[v]: u = fa[u][i] if u == v: return u # 如果此时u和v刚好相等,那一定是最近公共祖先,直接返回即可 for i in range(19, -1, -1): # 两个一起往上找祖先 if fa[u][i] != fa[v][i]: # 如果两个祖先不一样,就继续往上走 u = fa[u][i] # 向上走到祖先 v = fa[v][i] return fa[u][0] ``` ### 应用示例 在洛谷 P3379 【模板】最近公共祖先LCA)这道题中可以使用倍增法求解。该算法还可用于计算树上两点间的距离,公式为$d(u, v) = h(u) + h(v) - 2h(LCA(u, v))$,其中$d$是树上两点间的距离,$h$代表某点到树根的距离 [^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值