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

##题目大意:
题目链接:https://www.luogu.org/problemnew/show/P3379
给出一个树和 m m m组询问,对于每个询问输出两个结点的 L C A LCA LCA


##思路:
思路一:树上倍增
树上倍增是 L C A LCA LCA的基本方法之一。其做法是先将 x x x y y y跳到统一深度上,再利用倍增思想找到两点的 L C A LCA LCA
所以要先用 D F S DFS DFS求出每个点的深度以及它的祖宗,然后在对于每一询问完成上述操作即可。


思路二: S T ST ST
对于一棵树上的两个不同节点 x x x y y y,从根开始跑一遍欧拉序,设 x x x在欧拉序中第一次出现的位置为 f i r s t [ x ] first[x] first[x] y y y第一出现的位置是 f i r s t [ y ] first[y] first[y],那么 x x x y y y L C A LCA LCA就是欧拉序中 f i r s t [ x ] first[x] first[x] f i r s t [ y ] first[y] first[y]之间深度最低的(即 m i n ( d e p [ i ] ) min(dep[i]) min(dep[i]))节点就是它们的 L C A LCA LCA
证明略。但是很明显是正确的。
所以,先跑一边 D F S DFS DFS求出 f i r s t [ i ] first[i] first[i]和欧拉序,再利用 S T ST ST表求出任意区间的最小值( L C A LCA LCA),然后再读入输出即可。
注意!此方法时间较慢,需要输入输出流才能过!


##代码:
树上倍增:


#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#define N 500030
#define LG 25
using namespace std;

int dep[N+1],head[N+1],lg[LG+1],k,n,m,s,x,y,f[N+1][LG+1];

struct edge  //邻接表
{
	int to,next;
}e[N<<1+1];

void add(int from,int to)
{
	e[++k].to=to;
	e[k].next=head[from];
	head[from]=k;
}

void dfs(int x,int fa)  //求每个点的深度
{
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	for (int i=1;(1<<i)<=dep[x];i++)
	 f[x][i]=f[f[x][i-1]][i-1];  //树上倍增祖宗
	for (int i=head[x];i;i=e[i].next)
	 if (e[i].to!=fa)
	  dfs(e[i].to,x);
}

int lca(int x,int y)  //倍增
{
	if (dep[y]>dep[x]) swap(x,y);
	for (int i=LG;i>=0;i--)
	 if (dep[f[x][i]]>=dep[y]) x=f[x][i];  //到达同一高度
	if (x==y) return x;
	for (int i=LG;i>=0;i--)  //倍增
	 if (f[x][i]!=f[y][i])
	 {
	 	x=f[x][i];
	 	y=f[y][i];
	 }
	return f[x][0];
}
int main()
{
	//freopen("lca.in","r",stdin);
	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);
	}
	lg[1]=0;lg[2]=1;
	for (int i=3;i<=LG;i++)
	 lg[i]=lg[i>>1]+1;  //预处理
	dfs(s,0);
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",lca(x,y));
	}
	return 0;
}

S T ST ST表:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 500020
#define LG 20
using namespace std;

int first[N<<1+1],dep[N<<1+1],vis[N<<1+1],rmq[N<<1+1][LG],num[N<<1+1][LG],head[N<<1/+1];
int n,m,s,k,sum,x,y,z;

struct edge
{
	int next,to;
}e[N<<1+1];

int f;
char c;

int read()  //输入流
{
    f=0;
    while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
    return f;
}

void write(int x)  //输出流(可以不用)
{
    if(x>9) write(x/10);else putchar(x%10+48);
    return;
}

void add(int from,int to)
{
	k++;
	e[k].to=to;
	e[k].next=head[from];
	head[from]=k;
}

void dfs(int x,int k)  //求欧拉序
{
	sum++;
	first[x]=sum;
	dep[sum]=k;
	vis[sum]=x;
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (first[v]) continue;  //这个点已经到达过
		dfs(v,k+1);
		sum++;
		dep[sum]=k;
		vis[sum]=x;
	}
	return;
}

int main()
{
	//freopen("q.txt","r",stdin);
	memset(head,-1,sizeof(head));
	//scanf("%d%d%d",&n,&m,&s);
	n=read();
	m=read();
	s=read();
	for (int i=1;i<n;i++)
	{
		//scanf("%d%d",&x,&y);
		x=read();
		y=read();
		add(x,y);
		add(y,x);
	}
	
	dfs(s,1);
	
	for (int i=1;i<=sum;i++)  //预处理
	{
		rmq[i][0]=dep[i];
		num[i][0]=vis[i];
	}
	for (int j=1;j<=log2(sum);j++)
	 for (int i=1;i+(1<<j)-1<=sum;i++)  //ST表,RMQ
	  if (rmq[i][j-1]<rmq[i+(1<<(j-1))][j-1])
	  {
	  	 rmq[i][j]=rmq[i][j-1];
	  	 num[i][j]=num[i][j-1];
	  }
	  else
	  {
	  	 rmq[i][j]=rmq[i+(1<<(j-1))][j-1];
	  	 num[i][j]=num[i+(1<<(j-1))][j-1];
	  }	  
	for (int i=1;i<=m;i++)
	{
		//scanf("%d%d",&x,&y);
		x=read();
		y=read();
		if (first[x]>first[y]) swap(x,y);
		x=first[x];
		y=first[y];
		z=log2(y-x+1);  //求log
		if (rmq[x][z]>rmq[y-(1<<z)+1][z]) 
		 printf("%d\n",num[y-(1<<z)+1][z]);
		else printf("%d\n",num[x][z]);
   	    //putchar(10);
	}
	getchar();getchar();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值