先看一个树剖的经典应用:
初始化:
先DFS一遍子树,统计出每一个点x的重儿子son[x]和以x为根节点的子树的大小siz[x],这里选择x中子树大小最大的儿子作为它的重儿子,第二遍dfs划分树链:重儿子与其父亲节点划分到一条链。其他的儿子为x的轻儿子,但属于新的链的顶端元素。
void dfs1(int x,int father,int dep)//主要统计siz和son
{
fa[x]=father,deep[x]=dep,siz[x]=1;
for(ri k=fst[x];k>0;k=nxt[k])
if(v[k]!=father)
{
dfs1(v[k],x,dep+1);
if(siz[v[k]]>cur[x]) son[x]=v[k],cur[x]=siz[v[k]];//寻找子树大小最大的儿子作为x的重儿子
siz[x]+=siz[v[k]];
}
}
void dfs2(int x,int anc)//划分树链
//anc:点x所属的链的顶端节点
{
top[x]=anc;//top[x]记录点x所在的链的顶端元素的序号。此即x的重儿子和x属于一条重链
if(son[x]!=0) dfs2(son[x],anc);//防止死循
for(ri k=fst[x];k>0;k=nxt[k])
if(v[k]!=fa[x]&&v[k]!=son[x]) dfs2(v[k],v[k]);
}
初始完之后如何求LCA(x,y):
当x和y不属于一条链时,将所属链的顶端节点深度更大(deep[top[]]值更大的)的点向上提。直到x和y属于一条链为止。此时它们的LCA就是x和y中深度更浅的那个。
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<=deep[top[y]]) { y=fa[top[y]]; continue; }
if(deep[top[x]]>deep[top[y]]) { x=fa[top[x]]; continue; }
}
if(deep[x]>=deep[y]) return y;
return x;
}
这样我们就实现完了树剖求LCA。
完整代码(P3379 【模板】最近公共祖先(LCA)):
#include<cstdio>
#include<iostream>
#define ri register int
using namespace std;
const int MAXN=1000020;
int n,q,s,m,u[MAXN],v[MAXN],fst[MAXN],nxt[MAXN],xi,yi;
int fa[MAXN],deep[MAXN],siz[MAXN],cur[MAXN],son[MAXN],top[MAXN];
void dfs1(int x,int father,int dep)
{
fa[x]=father,deep[x]=dep,siz[x]=1;
for(ri k=fst[x];k>0;k=nxt[k])
if(v[k]!=father)
{
dfs1(v[k],x,dep+1);
if(siz[v[k]]>cur[x]) son[x]=v[k],cur[x]=siz[v[k]];
siz[x]+=siz[v[k]];
}
}
void dfs2(int x,int anc)
{
top[x]=anc;
if(son[x]!=0) dfs2(son[x],anc);
for(ri k=fst[x];k>0;k=nxt[k])
if(v[k]!=fa[x]&&v[k]!=son[x]) dfs2(v[k],v[k]);
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<=deep[top[y]]) { y=fa[top[y]]; continue; }
if(deep[top[x]]>deep[top[y]]) { x=fa[top[x]]; continue; }
}
if(deep[x]>=deep[y]) return y;
return x;
}
int main()
{
scanf("%d%d%d",&n,&q,&s);
m=(n-1)<<1;
for(ri i=1;i<=m;i+=2)
{
scanf("%d%d",&u[i],&v[i]);
nxt[i]=fst[u[i]],fst[u[i]]=i;
u[i+1]=v[i],v[i+1]=u[i];
nxt[i+1]=fst[u[i+1]],fst[u[i+1]]=i+1;
}
dfs1(s,s,0);
dfs2(s,s);
for(ri i=1;i<=q;i++)
{
scanf("%d%d",&xi,&yi);
cout<<LCA(xi,yi)<<'\n';
}
return 0;
}