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

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

洛谷 P3379

题目

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


输入

第一行包含三个正整数 N N N, M M M, S S S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 N N N−1 行每行包含两个正整数 x x x, y y y,表示 x x x 结点和 y y y 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 M M M 行每行包含两个正整数 a a a, b b b,表示询问 a a a 结点和 b b b 结点的最近公共祖先。


输出

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


样例

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

output
4
4
1
4
4


说明/提示

对于 30% 的数据, N N N≤10, M M M≤10。
对于 70% 的数据, N N N≤10000, M M M≤10000。
对于 100% 的数据, N N N≤500000, M M M≤500000。


样例说明:

该树结构如下:
洛谷 P3379 【模板】最近公共祖先(LCA)_第1张图片第一次询问:2,4 的最近公共祖先,故为 4。
第二次询问:3,2 的最近公共祖先,故为 4。
第三次询问:3,5 的最近公共祖先,故为 1。
第四次询问:1,2 的最近公共祖先,故为 4。
第五次询问:4,5 的最近公共祖先,故为 4。
故输出依次为 4,4,1,4,4。


解题思路

假设我们要求 x x x, y y y的最小公共祖先
很容易想到先让深度大的那个往上走
x x x, y y y深度一样后,再同时一步一步再接着一步
直到找到一个共同的祖先
但是这样的查询一次就是 O O O n n n

看题目就知道要学一个东东叫 L C A LCA LCA
也就是用来求最小公共祖先的算法
先学一下倍增这个概念
通过倍增可以想到,我们一次可以跳不止一步
我们可以让它一次就跳2^ j j j步(保证跳完之后没有超过边界,也不会跳过最小的公共祖先)
f f f[ i i i][ j j j]以 i i i为当前节点,跳了2^ j j j步后对应的节点

  • f f f[ i i i][0]=它的父节点
  • f f f[ i i i][ j j j]= f f f[ f f f[ i i i][ j j j-1]][ j j j-1] 跳了一半再一半

代码

#include
#include
#include
using namespace std;
struct hhx{
	int to,next;
}a[1000100];
int t,n,m,s,x,y;
int f[500100][50],head[500100],dep[500100];
void add(int x,int y)  //连接 
{
	 a[++t].to=y;
	 a[t].next=head[x];
	 head[x]=t;
}
void dfs(int d,int fa)
{
	 f[d][0]=fa;   //走一步,到父节点 
	 dep[d]=dep[fa]+1;  //累加深度 
	 for (int i=head[d];i;i=a[i].next)  //枚举子节点 
	     if (a[i].to!=fa) dfs(a[i].to,d);  //继续往下走 
}
int lca(int x,int y)
{
	if (dep[x]>dep[y])
	   swap(x,y);
	int c=dep[y]-dep[x],j=19,t=1<<j;
	while (c)
	{
		  if (c>=t)
		  {
		  	 y=f[y][j];
		  	 c-=t;
		  }
		  t/=2;
		  j--;
	}  //使x,y深度相同
	if (x==y)  //x就是x,y的最小公共祖先
	   return x;
	j=19;
	while (j>=0)  //同时走
	{
		  if (f[x][j]!=f[y][j])
		  {
		  	 x=f[x][j];
		  	 y=f[y][j];
		  }
		  j--;
	}
	return f[x][0];
} 
int main()
{
	scanf("%d%d%d",&n,&m,&s); 
	for (int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(s,0);
	for (int j=1;j<20;j++)  //枚举步数,2^20够了
	    for (int i=1;i<=n;i++)  //枚举点 
		    f[i][j]=f[f[i][j-1]][j-1]; //等于以走了一半的那个点作为起点,再走了一半 
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	} 
} 

你可能感兴趣的:(洛谷,LCA)