lca 科技

完全二叉树求 lca

学习自 https://blog.csdn.net/kksleric/article/details/7836649

先考虑完全二叉树的 lca 求法。中序遍历分配编号。

设第 kk 位是 uvu\oplus v 最左边的 11,则 lca(u,v)lca(u,v)u,vu,vkk 位以左、第 kk 位是 11kk 位以右是 00

将树上 lca 转到完全二叉树上。

先序遍历,设 huh_u 表示 dfnudfn_u 的末尾连续 00 数,lul_u 表示 uu 子树内最大 huh_u 的值,mxumx_u 表示 lul_u 对应的 dfnudfn_u 。相等的 mxumx_u 形成若干条链。有若 tptpuu 的祖先,mxtpmx_{tp}mxumx_u 完全二叉树中的祖先。

tp=lca(u,v)tp=lca(u,v)。则 mxtpmx_{tp}mxu,mxvmx_u,mx_v 的公共祖先。树上根到节点路径上 lul_u 单调不降,但在树上的 mxumx_u 值不连续。设 aua_u 表示从根到 uu 哪些 lul_u 值存在。可以求出 mxumx_umxvmx_v 在完全二叉树上的 lca 的末尾零数 dd,在 aua_uava_v 中都出现的最小的 sds\ge d 就是 mxtpmx_{tp} 的末尾零数。

每种 mxumx_u 值都会形成一条链。找到 u,vu,v 到链 mxtpmx_{tp} 上的最近祖先深度较小的一个即为 lca。可以找到 au,ava_u,a_v 中小于 ss 的最大的 du,dvdu,dv,这条链的链顶的父亲在链 mxtpmx_{tp} 上。

预处理 O(n)O(n),查询 O(1)O(1),空间 O(n)O(n)。理论上完全偏序所有 lca 算法。

int idx,dfn[maxn],rnk[maxn],l[maxn],fa[maxn],dep[maxn];
void dfs(int u){
	dep[u]=dep[fa[u]]+1;
	rnk[dfn[u]=++idx]=u;l[u]=__builtin_ctz(idx);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa[u])continue;
		fa[v]=u;dfs(v);
		l[u]=max(l[u],l[v]);
	}
}
int tp[maxn];unsigned int a[maxn];
void dfs1(int u,int lst){
	a[u]=a[fa[u]]|(1<<l[u]);tp[u]=lst;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa[u])continue;
		if(l[u]==l[v])dfs1(v,lst);
		else dfs1(v,v);
	}
}
int lca(int u,int v){
	if(u==v)return u;
	int d=31^__builtin_clz(dfn[u]^dfn[v]);
	int s=__builtin_ctz(((a[u]&a[v])>>d)<<d);
	int du=31^__builtin_clz(a[u]<<(32-s)>>(32-s)),uu=(l[u]!=s?fa[tp[rnk[((dfn[u]>>du)|1)<<du]]]:u);
	int dv=31^__builtin_clz(a[v]<<(32-s)>>(32-s)),vv=(l[v]!=s?fa[tp[rnk[((dfn[v]>>dv)|1)<<dv]]]:v);
	if(dep[uu]<dep[vv])return uu;
	return vv;
}

dfn 序 ST 表

uu 的 dfn 序到 vv 的 dfn 序之间必然不存在存在 lca(u,v)lca(u,v)lca(u,v)lca(u,v) 的祖先,必然存在 lca(u,v)lca(u,v) 的儿子。uu 的 dfn 序到 vv 的 dfn 序之间深度最小的点的父亲为 lca(u,v)lca(u,v)

预处理 O(nlogn)O(n\log n),查询 O(1)O(1),空间 O(nlogn)O(n\log n)

int dfn[maxn],idx;
int st[20][maxn];
void dfs(int u,int fa){
	st[0][dfn[u]=++idx]=fa;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v!=fa)dfs(v,u);
	}
}
int lca(int u,int v){
	if(u==v)return u;
	u=dfn[u],v=dfn[v];
	if(u>v)swap(u,v);u++;
	int k=log2(v-u+1);
	if(dfn[st[k][u]]<dfn[st[k][v-(1<<k)+1]])return st[k][u];
	return st[k][v-(1<<k)+1];
}
void init(){
	for(int j=1;j<20;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			if(dfn[st[j-1][i]]<dfn[st[j-1][i+(1<<j-1)]])st[j][i]=st[j-1][i];
			else st[j][i]=st[j-1][i+(1<<j-1)];
		}
	}
}

倍增

预处理 O(nlogn)O(n\log n),查询 O(logn)O(\log n),空间 O(nlogn)O(n\log n),常数大。

int dep[maxn],f[maxn][21];
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;f[u][0]=fa;
	for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;if(v==fa)continue;
		dfs(v,u);
	}
}
int lca(int x,int y){
	if(dep[x]>dep[y])swap(x,y);
	for(int i=20;i>=0;i--)if(dep[f[y][i]]>=dep[x])y=f[y][i];
	if(x==y)return x;
	for(int i=20;i>=0;i--)if(f[y][i]!=f[x][i])x=f[x][i],y=f[y][i];
	return f[x][0];
}

树剖

预处理 O(n)O(n),查询 O(logn)O(\log n),极难卡满,空间 O(n)O(n)

int dep[maxn],fa[maxn],siz[maxn],son[maxn];
void dfs(int u){
	dep[u]=dep[fa[u]]+1;siz[u]=1;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v!=fa[u]){
			fa[v]=u;
			dfs(v);
			siz[u]+=siz[v];
			if(siz[v]>siz[son[u]])son[u]=v;
		}
	}
}
int dfn[maxn],idx,rnk[maxn],tp[maxn];
void dfs2(int u,int lst){
	rnk[dfn[u]=++idx;]=u;tp[u]=lst;
	if(!son[u])return ;
	dfs2(son[u],lst);
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v!=fa[u]&&v!=son[u])dfs2(v,v);
	}
}
int lca(int u,int v){
	while(tp[u]!=tp[v]){
		if(dep[tp[u]]<dep[tp[v]])swap(u,v);
		u=fa[tp[u]];
	}
	if(dep[u]>dep[v])swap(u,v);
	return u;
}