【模板】最近公共祖先LCA——倍增

题目来自洛谷P3379 【模板】最近公共祖先(LCA)

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

题目描述

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

输入格式

第一行包含三个正整数 N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N1 行每行包含两个正整数 x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 M 行每行包含两个正整数 a,b,表示询问 a 结点和 b 结点的最近公共祖先。

输出格式

输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。

样例 #1

样例输入 #1

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

样例输出 #1

4
4
1
4
4

提示

对于 30% 的数据,N10M10

对于 70% 的数据,N10000M10000

对于 100% 的数据,1N,M5000001x,y,a,bN不保证 ab

样例说明:

该树结构如下:

第一次询问:2,4 的最近公共祖先,故为 4

第二次询问:3,2 的最近公共祖先,故为 4

第三次询问:3,5 的最近公共祖先,故为 1

第四次询问:1,2 的最近公共祖先,故为 4

第五次询问:4,5 的最近公共祖先,故为 4

故输出依次为 4,4,1,4,4

2021/10/4 数据更新 @fstqwq:应要求加了两组数据卡掉了暴力跳。

最近公共祖先——树上倍增

预处理(倍增) O(nlogn)

fa[i,j]表示从i结点出发,向上跳2j步所抵达的节点编号;
求解方式:
j=0,fa[i,0]=i
j>0, i向上跳2i1步后再跳2i1步就可以跳到2i的位置,所以0<jMaxfa[i,j]=fa[fa[i,j1],j1]
depth[i]表示结点i的深度;
哨兵: depth[0]=0;fa[i,j]=0, 表示i向上跳2j步跳出根结点的范围。
实现方式: DFSBFS

查询 O(logn)

查询分两步走:
1先将两个点跳到同一层
该步骤可以使用二进制拼凑的方式来实现。假设depth[x]>depth[y],则两者之间的差距为k=depth[x]depth[y],由于是按照2的整数幂向上跳,所以可以通过取k的二进制表示中1所在的位对应的权值进行跳跃即可将x跳到与y同一层。
在实现过程中,可以从大到小枚举指数e,当depth[fa[x][e]]>=depth[y]时,x=fa[x][e], 这样当x跳到与y同一层时就会停止跳跃。
如果在这一步后x==y, 则直接返回x.

2两个点同时向上跳,直到跳到最近公共祖先的下一层
为什么是下一层?因为如果直接判断是否跳到同一个节点,无法判断是否为最近的公共祖先,而有可能只是一个公共祖先。
判断条件: 从大到小枚举指数i, fa[x,i]fa[y,i], x=fa[x,i],y=fa[y,i]
fa[x,i]==fa[y,i]时,证明此时x,y已经跳到了最近公共祖先的下一层, 此时f[x][0]f[y][0]就是答案。

#include<bits/stdc++.h>
const int N = 500050;
int n, m, s;
std::vector<std::vector<int>> g(N);
int f[N][22];
int depth[N];
void pre(int root){
memset(depth, 0x3f, sizeof depth);
std::queue<int> q;
q.push(root);
depth[root] = 1;
depth[0] = 0;
while(!q.empty()){
auto u = q.front();
q.pop();
for(auto v:g[u]){
if(depth[v] > depth[u] + 1){
depth[v] = depth[u] + 1;
f[v][0] = u;
q.push(v);
for(int k = 1; k <= 20; k ++ ){
f[v][k] = f[f[v][k - 1]][k - 1];
}
}
}
}
}
int lca(int a, int b){
if(depth[a] < depth[b]) std::swap(a, b);
for(int k = 20; k >= 0; k -- ){
if(depth[f[a][k]] >= depth[b]){
a = f[a][k];
}
}
if(a == b) return a;
for(int k = 20; k >= 0; k -- ){
if(f[a][k] != f[b][k]){
a = f[a][k];
b = f[b][k];
}
}
return f[a][0];
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cin >> n >> m >> s;
for(int i = 0; i < n - 1; i ++ ){
int x, y;
std::cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
pre(s);
while(m -- ){
int a, b;
std::cin >> a >> b;
int anc = lca(a, b);
std::cout << anc << '\n';
}
return 0;
}

__EOF__

本文作者天涯海角寻天涯
本文链接https://www.cnblogs.com/yjx-7355608/p/17694445.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   天涯海角寻天涯  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示