Loading

浅谈 LCA

定义

祖先:在一个有根树中,一个点到根节点的路径中,经过的所有点都成为它的祖先(自己也是自己的祖先)。

最近公共祖先:给出两个点,在它们公共的祖先中,离两个点最近的就是这两个点的最近公共祖先,即 LCA。

在这个图中,两个红色点的 LCA 就是绿色点。

解法

向上标记法

从一个点向上走,每走到一个点标记一下,再从另一个点走,第一次走到被标记的那个点就是 LCA。

时间复杂度:\(O(n)\)

倍增法

我们先预处理每个点向上走 \(2^k(k\in \Z)\) 步后到达的点是谁。设 \(f(i,j)\) 为从 \(i\) 开始向上走 \(2^j\) 步到达的点,\(j\in[0,\lfloor \log n \rfloor]\)。由于跳 \(2^k\) 步等价于跳 \(2^{k-1}\) 步再跳 \(2^{k-1}\) 步,所以我们可以递推预处理。所以可以得到递推式:\(f(i,j)=f[f(i,j-1),j-1]\)

\(\text{dep}(i)\) 表示 \(i\) 这个点的深度,根节点的深度为 \(1\),那么每个点的深度就是到根节点的距离加一。

特别地,如果从 \(i\) 开始跳 \(2^k\) 步会跳出根节点,那么规定 \(f(i,k)=0\)。同时规定 \(\text{dep}(0)=0\)

两个点 \(x,y\) ,下面是求 LCA 的步骤:

  1. 如果 \(\text{dep}(x)<\text{dep}(y)\),那么将 \(x\)\(y\) 的值交换。

  2. 先将两个点跳到同一层,即将较深的那个点跳到较浅的那一层。这基于二进制拼凑,如果 \(\text{dep}(f(x,k))<\text{dep}(y)\),就不跳,否则就跳,直到 \(\text{dep}(x)=\text{dep}(y)\)

  3. 两个点同时往上跳,直到跳到 LCA 的下一层,也就是跳到 LCA 的儿子。因为如果跳到 LCA 的话,我们无法判断这是最近公共祖先还是只是公共祖先。直白一点,如果程序中,\(f(x,k)=f(y,k)\),只能说明这是祖先,但如果 \(f(x,k)\ne f(y,k)\),那么这个点一定在 LCA 的下面,所以一定可以跳,那就说明按照这个程序执行后,这两个点一定是 LCA 的儿子。

  4. 选一个点再跳一步,那么这个点就是 LCA。

预处理时间复杂度:\(O(n\log n)\)

查询时间复杂度:\(O(\log n)\)

例题

题目

模板题,可以当模板代码记:

#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=40010,M=100010;
int n,m;
int h[N],e[M],ne[M],idx;
int dep[N],fa[N][16];
int q[N];
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
void bfs(int root){
	mems(dep,0x3f);
	dep[0]=0;
	dep[root]=1;
	int hh=0,tt=0;
	q[0]=root;
	while(hh<=tt){
		int t=q[hh++];
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(dep[j]>dep[t]+1){
				dep[j]=dep[t]+1;
				q[++tt]=j;
				fa[j][0]=t;
				for(int k=1;k<=15;k++) fa[j][k]=fa[fa[j][k-1]][k-1];
			}
		}
	}
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int k=15;k>=0;k--)
		if(dep[fa[x][k]]>=dep[y]) x=fa[x][k];
	if(x==y) return x;
	for(int k=15;k>=0;k--)
		if(fa[x][k]!=fa[y][k]){
			x=fa[x][k];
			y=fa[y][k];
		}
	return fa[x][0];
}
int main(){
	cin>>n;
	int root=0;//根节点
	mems(h,-1);
	for(int i=0;i<n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		if(b==-1) root=a;
		else{
			add(a,b);
			add(b,a);
		}
	}
	bfs(root);//预处理
	cin>>m;
	while(m--){
		int a,b;
		scanf("%d%d",&a,&b);
		int p=lca(a,b);
		if(p==a) puts("1");
		else if(p==b) puts("2");
		else puts("0");
	}
	return 0;
}

附洛谷模板题代码:

#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=500010,M=1000010;
int n,m,root;
int h[N],e[M],ne[M],idx;
int dep[N],fa[N][21];
int q[N];
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
void bfs(int root){
	mems(dep,0x3f);
	dep[0]=0;
	dep[root]=1;
	q[0]=root;
	int hh=0,tt=0;
	while(hh<=tt){
		int t=q[hh++];
		for(int i=h[t];~i;i=ne[i]){
			int j=e[i];
			if(dep[j]>dep[t]+1){
				dep[j]=dep[t]+1;
				q[++tt]=j;
				fa[j][0]=t;
				for(int k=1;k<=20;k++) fa[j][k]=fa[fa[j][k-1]][k-1];
			}
		}
	}
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int k=20;k>=0;k--)
		if(dep[fa[x][k]]>=dep[y]) x=fa[x][k];
	if(x==y) return x;
	for(int k=20;k>=0;k--)
		if(fa[x][k]!=fa[y][k]){
			x=fa[x][k];
			y=fa[y][k];
		}
	return fa[x][0];
}
int main(){
	mems(h,-1);
	cin>>n>>m>>root;
	for(int i=1;i<n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	bfs(root);
	while(m--){
		int a,b;
		scanf("%d%d",&a,&b);
		printf("%d\n",lca(a,b));
	}
	return 0;
}
posted @ 2025-07-25 20:52  liushuangning  阅读(19)  评论(0)    收藏  举报