最近公共祖先 树链剖分

例题:洛谷P3379 【模板】最近公共祖先(LCA)
https://www.luogu.com.cn/problem/P3379

首先是几个概念
重儿子:父结点所有子树中最大的子树的根节点(只有一个或没有)
轻儿子:父结点除了重儿子以外的所有子结点(没有或有很多个)
重边:父结点和重儿子连的边
轻边:父结点和轻儿子连的边
重链:相接重边连成的路径

定理:
1.一棵树能被剖分成若干条重链
2.轻儿子一定是重链的顶点(轻儿子自己也可以成一“条”重链)
3.任意一条路径都能被切分成不超过logn条重链(也是时间复杂度的依据)

u和v的路径上深度最小的点也就是最近公共祖先,如果在一条重链上,显然u和v中深度较小的点就是lca;
如果不在一条重链上,因为一定有一条重链是和lca连着的,lca又一定是其他轻儿子的父结点,这些轻儿子各自又是一条重链的top,
那么:如果都在轻儿子为根的子树,显然能都跳到lca;如果一个在重儿子为根的子树,一个在轻儿子为根的子树,显然能让前者至少跳到lca为top的重链,后者跳到lca。

时间复杂度是O(n+mlogn),不过用起来似乎比tarjan快?

初始化完后基本思路就是让u,v跳到同一条重链,谁在上谁就是lca

#include<iostream>
#include<vector>
#define forup(i,l,r) for(int i=l;i<=r;i++)
#define fordown(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
 
const int N=5e5+5;
vector<int> child[N];
int fa[N],dep[N];
int heavy_child[N],size_tree[N],chain_top[N];//分别是该节点的重儿子,以该节点为根的子树的结点数,该节点所在重链的顶点 
inline int read()
{
	int n=0;
	char m=getchar();
	while(m<'0'||m>'9') m=getchar();
	while(m>='0'&&m<='9') {
		n=(n<<1)+(n<<3)+(m^'0');
		m=getchar();
	}
	return n;
}
void dfs_1(int father,int u)//初始化fa,dep,heavy_child,size(其实就是用来推重儿子)数组 
{
	fa[u]=father;
	dep[u]=dep[father]+1;
	size_tree[u]=1;
	for(int v:child[u])
	{
		if(v==father) continue;
		dfs_1(u,v);
		size_tree[u]+=size_tree[v];//u结点为根的树的大小就是其所有子树大小之和 
		if(size_tree[heavy_child[u]]<size_tree[v]) heavy_child[u]=v;//同步更新重儿子 
	}
}
void dfs_2(int top,int u)//初始化top数组 
{
	//先找重儿子、重边
	chain_top[u]=top;
	if(child[u].empty()) return;//一直到叶结点 
	dfs_2(top,heavy_child[u]);//继续找重儿子 
	//重儿子找过了就再找轻儿子、轻边,相当于是再建了一个重链,因为每条重链的顶点都是轻儿子 
	for(int v:child[u])
	{
		if(v!=fa[u]&&v!=heavy_child[u])//除去重儿子和父结点其他结点就是轻儿子了 
		{
			dfs_2(v,v);
		}
	}
}
int lca(int u,int v)
{
	while(chain_top[u]!=chain_top[v])//到同一条重链上 
	{
		if(dep[chain_top[u]]>=dep[chain_top[v]]) u=fa[chain_top[u]];//让深度大的先跳,保证跳到重链上离两点最近的公共祖先 
		else v=fa[chain_top[v]];
	}
	return dep[u]<dep[v]?u:v;//已经到同一条重链上,谁在上面谁就是LCA
}
int main()
{
	int n,m,root;
	cin>>n>>m>>root;
	int u,v;
	forup(i,1,n-1)
	{
		u=read(),v=read();
		child[u].push_back(v);
		child[v].push_back(u);
	}
	dfs_1(0,root);
	dfs_2(root,root);
	forup(i,1,m)
	{
		u=read(),v=read();
		cout<<lca(u,v)<<endl;
	}
	return 0;
}

参考的代码lca函数循环里面原本是下面这个,swap操作是不是会慢一点呢,反正上面那个好明白一点点吧

	if(dep[chain_top[u]]<dep[chain_top[v]]) swap(u,v);
	u=fa[chain_top[u]];

参考:https://www.cnblogs.com/dx123/p/16320467.html

posted @ 2023-05-03 21:13  eternal_visionary  阅读(25)  评论(0编辑  收藏  举报