最近公共祖先模板

题目

最近公共祖先模板

DFS序求LCA

时间复杂度:\(O(N\log N+M)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int N,M,S,cnt,dfn[MAXN],st[20][MAXN],lg[MAXN];//dfn[x]:节点x的DFS序(从1开始);st[0][i]:DFS序为i的节点的父节点,这是后续ST表的基础;st[k][i]:区间[i,i+2^k-1]内dfn最小的节点(是父节点编号)
int hd[MAXN],tot,nxt[2*MAXN],ver[2*MAXN];
void add(int u,int v){
	ver[++tot]=v;nxt[tot]=hd[u];hd[u]=tot;
	ver[++tot]=u;nxt[tot]=hd[v];hd[v]=tot;
}
void dfs(int id,int f){//求出dfn,并记录父节点
	dfn[id]=++cnt;//给节点id分配DFS序
	st[0][dfn[id]]=f;//进入id时,其父节点为f,记录其父节点
	for(int i=hd[id];i;i=nxt[i]){
		if(ver[i]!=f) dfs(ver[i],id);
	}
}
int Get(int x,int y){//比较两个节点的dfn,返回dfn较小的节点
	return dfn[x]<dfn[y]?x:y;
}
int LCA(int u,int v){//O(1)查询LCA
	if(u==v) return u;
	u=dfn[u],v=dfn[v];
	if(u>v) swap(u,v);
	int d=lg[v-u];//区间长度为v-u,取对数
	u++;//左端点变为u+1,查询区间[u+1,v]
	return Get(st[d][u],st[d][v-(1<<d)+1]);//用两个长度2^d的区间覆盖[u+1,v]
}
int main(){
	ios::sync_with_stdio(0);cin.tie(0);
	cin>>N>>M>>S;
	for(int i=2,u,v;i<=N;i++){
		cin>>u>>v;
		add(u,v);
	}
	dfs(S,0);//根节点父亲记为0
	for(int i=2;i<=N;i++) lg[i]=lg[i>>1]+1;//预处理log2
	for(int i=1;i<=lg[N];i++){//建立ST表
		for(int j=1;j+(1<<i)-1<=N;j++){//第i层区间长度为2^i,用两个2^(i-1)的区间合并,取dfn较小的节点
			st[i][j]=Get(st[i-1][j],st[i-1][j+(1<<(i-1))]);
		}
	}
	for(int i=1,u,v;i<=M;i++){
		cin>>u>>v;
		cout<<LCA(u,v)<<'\n';
	}
	return 0;
}

树链剖分求LCA

时间复杂度:\(O(M\log N+N)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+5;
int hd[MAXN],tot,nxt[2*MAXN],ver[2*MAXN];
int N,M,S,dep[MAXN],sz[MAXN],zson[MAXN],fa[MAXN],top[MAXN];
void add(int u,int v){
	ver[++tot]=v;nxt[tot]=hd[u];hd[u]=tot;
	ver[++tot]=u;nxt[tot]=hd[v];hd[v]=tot;
}
void dfs1(int u,int f){//第一遍 DFS:求深度,父节点,子树大小,重儿子
	sz[u]=1;
	fa[u]=f;
	dep[u]=dep[f]+1;//深度=父节点深度+1
	for(int i=hd[u];i;i=nxt[i]){
		int v=ver[i];
		if(v!=f){
			dfs1(v,u);
			sz[u]+=sz[v];
			if(sz[zson[u]]<sz[v]||!zson[u]){//维护重儿子:子树最大的那个儿子或还没有设置重儿子
				zson[u]=v;
			}
		}
	}
}
void dfs2(int u,int t){//第二遍DFS:确定每条重链的顶端top
	top[u]=t;
	if(!zson[u]) return ;//叶子节点
	dfs2(zson[u],t);//先走重儿子,继承当前链顶
	for(int i=hd[u];i;i=nxt[i]){//遍历轻儿子,它们各自成为新链的顶端
		int v=ver[i];
		if(v!=fa[u]&&v!=zson[u]){//排除父节点和已经处理过的重儿子
			dfs2(v,v);//轻儿子作为新链的顶端,递归剖分
		}
	}
}
int LCA(int u,int v){//树剖求LCA:不断将深度较大的链顶向上跳
	while(top[u]!=top[v]){
		//选择链顶深度较大的节点向上跳
		if(dep[top[u]]<dep[top[v]]){
			swap(u,v);
		}
		u=fa[top[u]];//跳至链顶的父节点
	}
	return dep[u]<dep[v]?u:v;//此时两点在同一条重链上,深度较小的即为LCA
}
int main(){
	ios::sync_with_stdio(0);cin.tie(0);
	cin>>N>>M>>S;
	for(int i=2,u,v;i<=N;i++){
		cin>>u>>v;
		add(u,v);
	}
	dfs1(S,0);//以S为根
	dfs2(S,S);//根节点所在重链顶端为自己
	for(int i=1,u,v;i<=M;i++){
		cin>>u>>v;
		cout<<LCA(u,v)<<'\n';
	}
	return 0;
}
posted @ 2026-08-14 09:46  decentz  阅读(11)  评论(0)    收藏  举报