最近公共祖先 树链剖分
例题:洛谷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]];