题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数
N
,
M
,
S
N,M,S
N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来
N
−
1
N-1
N−1 行每行包含两个正整数
x
,
y
x, y
x,y,表示 x 结点和 y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来
M
M
M 行每行包含两个正整数
a
,
b
a, b
a,b,表示询问 a 结点和 b 结点的最近公共祖先。
输出格式
输出包含 M 行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
输入 #1
1 2 3
输出 #1
2
输入 #2
1 5 3
输出 #2
2
知识点:LCA问题:
定义:基于有根树最近公共祖先问题
在有根树T中,询问一个距离根最远的结点x,使得x同时为结点
u
、
v
u、v
u、v的祖先,这个祖先节点即为
l
c
a
lca
lca。同时
l
c
a
lca
lca一定是
u
、
v
u、v
u、v路径上的点。
解题思路
我们可以有这样一个思路,先让
u
、
v
u、v
u、v中深度大的那个点先往上走,直到两点深度相同。然后一起往上走,知道两点相遇,这个点就是
l
c
a
lca
lca。
接着我们想,如果我们每次只走一步,显然单次查询lca复杂度为 O ( n ) O(n) O(n)。那有没有在空间合适的条件下更好的算法呢?——我们可以用倍增 倍增 的思想
想要实现这个算法,首先我们要记录各个点的深度和他们
2
i
2^i
2i级的的祖先,用数组
d
e
p
dep
dep表示每个节点的深度,
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示节点
i
i
i的
2
j
2^j
2j级祖先。 这样如果我们需要走k步,那只需要跳
O
(
l
o
g
n
)
O(logn)
O(logn)次即可。
转移:
-
f
[
i
]
[
j
]
=
f
a
[
i
]
(
j
=
0
)
f[i][j]=fa[i] (j=0)
f[i][j]=fa[i](j=0)
f [ i ] [ j ] = f [ f [ i ] [ j − 1 ] ] [ j − 1 ] ( j > 0 ) f[i][j]=f[f[i][j-1]][j-1] (j>0) f[i][j]=f[f[i][j−1]][j−1](j>0)
这个转移可以说是算法的核心之一,意思是 i i i的 2 i 2^i 2i祖先等于 i i i的 2 ( i − 1 ) 2^(i-1) 2(i−1)祖先的 2 ( i − 1 ) 2^(i-1) 2(i−1)祖先 ,也就是走 2 j 2^j 2j步相当于先走 2 ( j − 1 ) 2^(j-1) 2(j−1)步再走 2 ( j − 1 ) 2^(j-1) 2(j−1)步。
代码如下:
void dfs(int x,int fa){//x表示当前节点,fa表示它的父亲节点
f[x][0]=fa,dep[x]=dep[fa]+1;
for(int i=1;i<=lg[dep[x]];i++)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=h[x];i;i=a[i].next)
if(a[i].x!=fa)
dfs(a[i].x,x);
}
预处理完毕后,我们就可以去找它的 L C A LCA LCA了,为了让它跑得快一些,我们可以加一个常数优化。。。(来自洛谷提高组讲义)
for(int i=1;i<=n;i++)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
接下来就是倍增 L C A LCA LCA了,按前面说到的我们先把两个点提到同一高度,再统一开始跳。
int LCA(int x,int y){
if(dep[x]>dep[y]) swap(x,y);//用数学语言来说就是:不妨设x的深度 <= y的深度
while(dep[y]>dep[x])
y=f[y][lg[dep[y]-dep[x]]-1];//先跳到同一深度
if(x==y)//如果x是y的祖先,那他们的LCA肯定就是x了
return x;
for(int i=lg[dep[x]]-1;i>=0;i--)//不断向上跳(lg就是常数优化)
{
if(f[x][i]!=f[y][i])//因为我们要跳到它们LCA的下面一层,所以它们肯定不相等,如果不相等就跳过去。
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];//返回父节点
}
AC代码
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,s,x,y,k,h[500010],lg[500010],dep[500010],f[500010][22];
struct c{
int x,next;
}a[1000010];
void add(int x,int y){
a[++k].x=y;
a[k].next=h[x];
h[x]=k;
}
void dfs(int x,int fa){
f[x][0]=fa,dep[x]=dep[fa]+1;
for(int i=1;i<=lg[dep[x]];i++)
f[x][i]=f[f[x][i-1]][i-1];
for(int i=h[x];i;i=a[i].next)
if(a[i].x!=fa)
dfs(a[i].x,x);
}
int LCA(int x,int y){
if(dep[x]>dep[y]) swap(x,y);
while(dep[y]>dep[x])
y=f[y][lg[dep[y]-dep[x]]-1];
if(x==y)
return x;
for(int i=lg[dep[x]]-1;i>=0;i--)
{
if(f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n-1;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
dfs(s,0);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
}