学习笔记:LCA,最近公共祖先

定义

最近公共祖先(Lowest Common Ancestor),简称LCA,是算法竞赛中常用的、用以查找树上两个结点中,距离根结点最远的结点的算法。

实现

朴素算法

过程

每次找深度比较大的那个点,让它向上跳。显然在树上,这两个点最后一定会相遇,相遇的位置就是想要求的 LCA。 或者先向上调整深度较大的点,令他们深度相同,然后再共同向上跳转,最后也一定会相遇。

复杂度

由于朴素算法在预处理时要dfs整棵树,因此预处理时间复杂度为O(N)
单次查询时间复杂度同样为O(N),在大部分题目的数据范围下都无法通过,所以代码就不贴了。

倍增实现LCA

这里往下才是本篇的重点。

我们发现,在朴素算法中每次两个点向上跳的过程会耗费大量的时间,因此可以考虑对其进行优化。
那么具体该如何实现呢?显然,可以考虑使用倍增。不了解倍增原理的可以自行在oi-wiki上查阅。
我们在找LCA前先预处理好一个二维数组fax,i,它表示结点x的第2i个祖先。这样便可以实现了。

过程

首先将我们要用到的数组等信息定义好:

const int N=2*1e5+10; 
struct Node{
    int u,v,nxt;
}edge[N];
int cnt=0;
int fa[N][32],dep[N],lg[N];
int head[N];//采用链式前向星存图
void add(int u,int v)
{
	edge[++cnt].u=u;
	edge[cnt].v=v;
	edge[cnt].nxt=head[u];
	head[u]=cnt;
}
void getlog(int n)
{
    for(int i=1;i<=n;i++)
    {
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    }
}

首先依旧要dfs整棵树,预处理好dep数组以及fa数组,而其中depi表示的是第i个结点的深度,fa数组在上文已经解释过。

void dfs(int now,int fath)//now为当前结点,fath为当前结点的父结点
{
    dep[now]=dep[fath]+1;//深度++;
    fa[now][0]=fath;//now结点的第2^0个祖先,即第一个祖先为fath
    for(int i=1;i<=lg[dep[now]];i++)
    {
        fa[now][i]=fa[fa[now][i-1]][i-1];//此处是倍增思想的体现,思考一下为什么
    }
    for(int i=head[now];i;i=edge[i].next)
    {
        if(edge[i].v!=fath) dfs(edge[i].v,now);//如果不重复就继续向下遍历
    }
}

到这里,我们的预处理就完成了,那么该如何来寻找两个结点的LCA呢?很简单,我们只需要先将两个点跳至同一深度,再用我们预先处理好的fa数组来向上找就OK了。具体可以结合代码

int LCA(int x,int y)
{
	if(dep[x]<dep[y])
	    swap(x,y);//为了方便处理,默认x的深度大于y
	while(dep[x]>dep[y])
	    x=fa[x][lg[dep[x]-dep[y]]-1];//向上跳,将两个点调整至同一深度
	if(x==y) return x;//若相等则直接返回
	for(int k=lg[dep[x]]-1;k>=0;k--)
	{
		if(fa[x][k]!=fa[y][k])
		{
			x=fa[x][k];
			y=fa[y][k];
		}//将二者同时向上跳,最多跳lg[dep[x]]-1次
	}
	return fa[x][0];//返回此时x父节点的值,即x与y的LCA
}

贴一份完整代码

#include<bits/stdc++.h>
#define life Elaina
#define angle Exusiai
#define int long long
using namespace std;

const int N=6*1e5+10;

struct Node{
	int u,v,nxt;
}edge[N*2];
int cnt=0;
int fa[N][32],dep[N],lg[N];
int head[N];

void add(int u,int v)
{
	edge[++cnt].u=u;
	edge[cnt].v=v;
	edge[cnt].nxt=head[u];
	head[u]=cnt;
}

void getlog(int n)
{
	for(int i=1;i<=n;i++)
	{
		lg[i]=lg[i-1]+(1<<lg[i-1]==i);
	}
}

void dfs(int now,int fath)
{
    dep[now]=dep[fath]+1;
	fa[now][0]=fath;
	for(int i=1;i<=lg[dep[now]];i++)
	{
		fa[now][i]=fa[fa[now][i-1]][i-1];
	}
	for(int i=head[now];i;i=edge[i].nxt)
	{
		if(edge[i].v!=fath) dfs(edge[i].v,now);
	}
}

int LCA(int x,int y)
{
	if(dep[x]<dep[y])
	    swap(x,y);
	while(dep[x]>dep[y])
	    x=fa[x][lg[dep[x]-dep[y]]-1];
	if(x==y) return x;
	for(int k=lg[dep[x]]-1;k>=0;k--)
	{
		if(fa[x][k]!=fa[y][k])
		{
			x=fa[x][k];
			y=fa[y][k];
		}
	}
	return fa[x][0];
}

signed main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n,m,s;
	cin>>n>>m>>s;
    for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	getlog(n);
	dfs(s,0);
    for(int i=1;i<=m;i++)
	{
		int a,b;
		cin>>a>>b;
		cout<<LCA(a,b)<<endl;
	}
	return 0;
}

本人的第一篇博客,轻点喷

posted @   Wolves_487  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
*/
点击右上角即可分享
微信分享提示