LCA模版

问题:输入一棵n个结点的树,以及m组请求,每个请求是两个结点,求它们的最小公共祖先。

实现思路:先建树,求出每一个点的深度,再根据深度来求lca。

具体实现:

        建树求深度,并求出每个点的父节点,方便后面进行跳点、

        接下来考虑如何查找最近公共祖先,可以先将两个点升到同一高度,再同时往上跳,但这么做的时间复杂度为O(nm)肯定会超时。这里我用ST表进行优化。

        优化思路:下面是一个树,如果我们要求6号节点和7号节点lca,我们可以先列出一个式子,表示第i层时他们的祖先节点是否是同一个点。

        如图列出式子如下:F F F T T T

        可以看出当第一次达到T时,该点为两点的最近公共祖先。

        那我们用ST表记录st[x][i]表示向上跳2^i个点,跳了之后如果该点为两点的祖先节点就换更小的i来跳,反之则将x,y更替为st[x][i],st[y][i]。通过这种方式来进行提速,单次lca的时间复杂度为O(logn),总体时间复杂度为O(mlogn)。

        代码如下:

//st表初始化
void init(){
  for(int i=1;i<=n;i++)st[i][0]=fa[i];
  for(int j=1;(1<<j)<=n;j++){
    for(int i=1;i<=n;i++)st[i][j]=st[st[i][j-1]][j-1];
  }
}

 

//lca模版
int lca(int x,int y){
  if(deep[x]<deep[y]){
    int tp=x;x=y;y=tp;
  }int t=deep[x]-deep[y],i=0;
  while(t){
    if(t%2==1)x=st[x][i];
    t=t>>1;i++;
  }if(x==y)return x;
  for(int i=logn;i>=0;i--){
    if(st[x][i]==st[y][i])continue;
    x=st[x][i];y=st[y][i];
  }return st[x][0];
}

 最后完整代码如下:

#include<cstdio>
#include<cmath>
using namespace std;
const int N=1e6+100;
struct node{
	int y,nx;
}e[N];
int n,m,last,logn;
int head[N],fa[N],deep[N],st[N][30];
void add(int x,int y){
	last++;
	e[last].y=y;
	e[last].nx=head[x];
	head[x]=last;
}void dfs(int x,int _fa){
	fa[x]=_fa;
	deep[x]=deep[_fa]+1;
	for(int i=head[x];i;i=e[i].nx){
		int y=e[i].y;
		if(y==_fa)continue;
		dfs(y,x);
	}
}void init(){
  for(int i=1;i<=n;i++)st[i][0]=fa[i];
  for(int j=1;(1<<j)<=n;j++)
    for(int i=1;i<=n;i++)st[i][j]=st[st[i][j-1]][j-1];
}int lca(int x,int y){
  if(deep[x]<deep[y]){
    int tp=x;x=y;y=tp;
  }int t=deep[x]-deep[y],i=0;
  while(t){
    if(t%2==1)x=st[x][i];
    t=t>>1;i++;
  }if(x==y)return x;
  for(int i=logn;i>=0;i--){
    if(st[x][i]==st[y][i])continue;
    x=st[x][i];y=st[y][i];
  }return st[x][0];
}int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);add(b,a);
	}dfs(1,0);init();
	logn=log2(n);
	for(int i=1;i<=m;i++){
		int a,b;
	    scanf("%d%d",&a,&b);
	    printf("%d\n",lca(a,b));
  	}return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值