#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<sstream>
#include<string>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<utility>
#define JOJO cout<<"JoJo"<<endl;
using namespace std;
const long long INF = ~0ull>>2;
const int Inf = 0x3f3f3f3f;
const int alphabet = 26;
const int maxn = 5e5+5;
int head[maxn]; //head[i]记录以i点为起点的最后一条边的下标
int depth[maxn], anc[maxn][25]; //depth[i]记录i点的深度,anc[i][j]记录i点的第2^j个祖先节点的序号
int t = 0;
int S; //S是树的根结点
/*edge[i].to记录边i的终点,
edge[i].next记录与边i起点相同的上一条边的下标*/
struct tagedge{
int to, next;
}edge[2*maxn];
void AddEdge(int u, int v)
{
edge[t].to = v;
edge[t].next = head[u];
head[u] = t++;
return;
}
void Dfs_init(int rt, int par) //rt指当前结点,par指当前结点的父结点
{
depth[rt] = depth[par]+1; //当前结点的深度是其父结点深度+1
anc[rt][0] = par; //当前结点的第2^0个即第1个结点,就是其父结点
/***倍增递推式:anc[rt][i] = anc[ anc[rt][i-1] ][i-1]**
当前结点的第 2^i 个祖先结点是其 2^(i-1) 个祖先结点的第 2^(i-1) 个祖先结点*/
for (int i = 1; i <= log(depth[rt]-1)/log(2) ; i++)
anc[rt][i] = anc[ anc[rt][i-1] ][i-1];
/*对以该点为起点的每条边的终点进行相同操作(求深度、求祖先结点)
因为是双向边,所以会出现终点为当前点的父结点的情况,跳过就好*/
for (int i = head[rt]; i != -1; i = edge[i].next){
int v = edge[i].to;
if (v != par)
Dfs_init(v, rt);
}
return;
}
void move_x(int& x, int y)
{
int h = depth[x] - depth[y]; //h是x结点和y结点相差的深度
/*用二进制数表示h,因为利用二进制数,可以将时间从n优化到logn,
结果与用十进制处理相同
for (int i = 1; i <= h; i++){
x = anc[x][0]; 每次都使x结点移动到它的父结点
}*/
for (int i = 0; h > 0; i++){
if (h & 1)
x = anc[x][i]; //移动x结点
h >>= 1;
}
return;
}
int LCA(int x, int y)
{
/*因为move_x()函数中默认x结点比y结点深,所以在之前进行比较和交换*/
if (depth[x] < depth[y])
swap(x, y);
/*因为要求出x结点和y结点的公共祖先结点,
那么它们最终肯定要在同一个点上,
这样的话,使x结点和y结点在深度上一起移动就比较方便,
所以先将x结点和y结点移动到一个深度上*/
move_x(x, y);
/*进行特判,
如果将x结点移动到与y结点同一深度之后两点重合,
说明y结点就是x结点的祖先结点,
因此x结点和y结点的最近公共祖先结点就是y结点*/
if (x == y)
return y;
/*从x结点和y结点的最远祖先结点开始判断,
比较两结点的祖先结点 xx 和 yy,
1、如果 xx 和 yy 相同,则xx和yy所在的这个结点是x结点和y结点的公共祖先结点,
但不一定是最近公共祖先结点,所以继续判断较近的祖先结点
2、如果 xx 和 yy 不同,则最近公共祖先结点一定是xx和yy的最近公共祖先结点,
问题则转化为求xx和yy的最近公共祖先结点
(即,将x结点和y结点分别移动到xx和yy的位置)
为什么从最远祖先结点开始判断,而不是从两结点的父结点(父结点距离两结点最近)开始判断?
若从两结点的父结点开始判断,
①利用anc[x][0, 1, 2, 3···],可能会跳过最近公共祖先结点,到较远的公共祖先结点,且此时很难返回
②利用anc[x][0]一个深度一个深度地寻找,效率太低*/
for (int i = log(depth[x])/log(2)+1; i >= 0; i--){
if (anc[x][i] != anc[y][i]){
x = anc[x][i]; //同时移动x结点和y结点,保证它们在同一深度上
y = anc[y][i];
}
}
return anc[x][0];
}
void init()
{
fill(head, head+maxn, -1);
fill(depth, depth+maxn, 0);
fill(anc[0], anc[0]+maxn*20, 0);
memset(edge, 0, sizeof(edge));
t = 0;
return;
}
int main()
{
init();
int N, M;
scanf("%d%d%d", &N, &M, &S);
for (int i = 1; i <= N-1; i++){
int x, y;
scanf("%d%d", &x, &y);
AddEdge(x, y); //建树,因为是无向边,所以x->y和y->x都要建边
AddEdge(y, x);
}
/*Dfs_init() 用dfs进行初始化,求出每个结点的深度和各祖先节点
从树的根开始初始化*/
Dfs_init(S, 0);
while (M--){
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", LCA(x, y)); //LCA(x, y) 求x和y的最近公共祖先结点
}
return 0;
}
洛谷P3379 【模板】最近公共祖先(LCA)(倍增法)
最新推荐文章于 2025-08-15 16:36:01 发布
本文深入讲解了最近公共祖先(LCA)算法的实现原理及应用,通过具体代码展示了如何求解树状结构中两个节点的最近公共祖先。文章首先介绍了算法的基本概念,随后详细解析了倍增法求解LCA的过程,包括初始化、深度遍历和查找公共祖先的具体步骤。
993

被折叠的 条评论
为什么被折叠?



