LCA及例题

倍增法求LCA

(蒟蒻只会用倍增)
简单说就是先通过dfs预处理出每个节点i的深度deep[i]与其的第\(2^j\)个祖先f[i][j]。求f[i][j]的关键在于递推式f[i][j]=f[f[i][j-1]][j-1]。也即i的 \(2^j\) 祖先是 \(2^{j-1}\) 祖先的 \(2^{j-1}\) 祖先。
再求\(LCA\):首先将两节点的高度提升到同一水平,再同时提升两节点直至其父节点相同。当然,为了保证效率,两次提升都应该通过倍增法进行。
最终空间复杂度O(nlogn),预处理时间O(nlogn),单次查询时间O(logn)

#include <cstdio>
#include <iostream>
using namespace std;

const int N=500000+10;
int m,n,s,cnt,dep[N],f[N][21],head[N],lg[N];//lg[N]是log(N)向上取整
struct Edge{
	int to,next;
}e[N];

void init_lg(int max_v){//在max_v范围内预处理出lg数组
	for(register int i=1;i<=max_v;i++)
		lg[i]=lg[i-1]+((i<<lg[i-1])==i);
}
void addEdge(int u,int v){//链式前向星存图
	cnt++;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
void dfs(int u,int ufa){//dfs预处理dep[i]
	dep[u]=dep[ufa]+1;
	f[u][0]=ufa;
	for(int i=1;i<=lg[dep[u]];i++)//递推计算u的2^i祖先
		f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].next)//递归处理u的所有子节点
		if(e[i].to!=ufa)
			dfs(e[i].to,u);
}
inline int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);//保证dep[u]≥dep[v]
	for(int i=lg[n];i>=0;i--){//将u提到和v同一深度
		if(dep[f[u][i]]>=dep[v]) u=f[u][i];
		if(u==v) return u;
	}
	for(int i=lg[n];i>=0;i--)//同时提升u,v直至其父节点相同
		if(f[u][i]!=f[v][i]){
			u=f[u][i];v=f[v][i];
		}
	return f[u][0];//其父节点即LCA(u,v)
}

int main(){
	int u,v;
	scanf("%d%d%d",&n,&m,&s);
	for(register int i=1;i<=n-1;i++){
		scanf("%d%d",&u,&v);
		addEdge(u,v);//存两次边
		addEdge(v,u);
	}
	init_lg(n+10);
	dfs(s,0);
	for(register int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		printf("%d\n",lca(u,v));
	}
	return 0;
}

LCA的应用

P3379 LCA模板
P3884 二叉树问题
题意:给一棵二叉树,求深度、宽度(节点最多层的节点数)和两点u,v的距离(定义为u到LCA的路径长乘2加上v到LCA的路径长)。

点击查看题解

分析:利用LCA的副产品deep[i],可以很容易求出最深度和宽度。至于距离,u,v与LCA的路径长就是其深度的差值,按题意计算即可。

P4281 紧急集合
题意:给出一棵树以及三个点(多组数据),求与三个点的距离之和最小的点及距离之和。

点击查看题解

分析:画出分析图后发现这个点一定是每两点之间最深的LCA。如图,假设u=LCA(a,b)是三个LCA中最深的,那么v=LCA(a,c)=LCA(b,c)一定不会比u更好(多了uv之间的长度)。当然也可以找出与其他两个不同的LCA(因为三点间的LCA至少有两个相等,而除非u=v,u一定不与其他两个相同)

至于距离,可以得出dis=deep[a]+deep[b]+deep[c]-deep[u]-2*deep[v]。又由u=LCA(a,b),v=LCA(a,c)=LCA(b,c),推出dis=deep[a]+deep[b]+deep[c]-deep[LCA(a,b)]-deep[LCA(b,c)]-deep[LCA(c,a)],这样也省去了分类讨论的麻烦。

P3398 仓鼠找sugar
题意:给出一颗树,以及四个数a,b,c,d(多组数据)。询问ab间最短路径与cd间最短路径是否相交。

点击查看题解

分析:显然最短路径就是两节点与LCA之间的路径。由于树的每一节点都只有一个父亲,那么两路径相交当且仅当一个路径的LCA在另一个路径上(否则交点就在两个路径上有两个父亲)。记L=LCA(u,v),那么如何判断一点p在路径u-L-v上呢?
这需要满足两个条件:1.deep[p]>=deep[L];2.LCA(p,u)=L或LCA(p,v)=L。当然,不必分两次判断,只需要先保证条件1成立(否则就交换)再判断条件2即可。

posted @ 2019-09-21 22:50  心元  阅读(210)  评论(0编辑  收藏  举报