题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 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)次即可。
转移:
这个转移可以说是算法的核心之一,意思是 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));
}
}