题解 P3379 【【模板】最近公共祖先(LCA)】
我发现一个严重的问题:树剖LCA写的人少,且基本都常数较大,貌似题解前六页的树剖LCA没有比我快的。 我在这里给大家讲一下树剖LCA。
首先,你要会树链剖分,如果不会请出门左转 洛谷P3384,然后我们直接两遍大法师统计信息,注意这里不用写线段树,因为我们并不需要统计权值,于是可以直接不写线段树,见图。
原谅我图画的丑
这图中蓝色和绿色的线条是重边,红色是表示我们要求这两点的LCA。
于是先判断一下,他们是不是在一条重链上,因为重链上两点的深度肯定不同,如果在同一重链上,LCA显然就是深度小的那个。我们一看,显然不同,于是我们可以想到将情况转化为在同一重链上来做。首先比较一下谁的链顶深度大,就向上跳。
我们给两点起个名字:x,y,从图中可以显然的看出(注意:轻儿子的链顶是他自己):depth[top[x]]>depth[top[y]](depth是深度)。 于是我们把y向上跳,即y=fa[top[y]](fa是父亲),发现他们在一条重链上了,就转化成了第一种情况,返回深度小的点即可。 贴一下核心代码:
int query(int x,int y)//核心代码
{
while(top[x]!=top[y])//判断是否在一条重链上
if(depth[top[x]]>=depth[top[y]])//链顶深度大的往上跳
x=fa[top[x]];
else
y=fa[top[y]];
return depth[x]<depth[y]?x:y;//返回深度小的节点编号
}
最后再贴一下AC代码(最大的点380ms,总共1.03s)
#include <bits/stdc++.h>
using namespace std;
const int MAXN=500005;
struct Edge
{
int from;
int to;
int next;
}e[MAXN<<1];//邻接表存图
int depth[MAXN],fa[MAXN],siz[MAXN],head[MAXN],son[MAXN],root,n,opt,cnt,top[MAXN];//son是重儿子,siz是子树大小,opt是询问数,其他同上文。
inline int read(void)//快读
{
char ch=getchar();
int mark=1,x=0;
while((ch<'0'||ch>'9')&&ch!='-')
ch=getchar();
if(ch=='-')
{
mark=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x*10)+(ch-'0');
ch=getchar();
}
return mark*x;
}
void dfs1(int p,int dep,int father)//统计子树大小、深度、父亲和重儿子。
{
depth[p]=dep;
fa[p]=father;
int maxv=-1;
siz[p]=1;
for(int i=head[p];i;i=e[i].next)
if(e[i].to!=father)
{
dfs1(e[i].to,dep+1,p);
siz[p]+=siz[e[i].to];
if(siz[e[i].to]>maxv)
{
maxv=siz[e[i].to];
son[p]=e[i].to;
}
}
return ;
}
void dfs2(int p,int tp)//统计链顶
{
top[p]=tp;
if(!son[p])
return ;
dfs2(son[p],tp);
for(int i=head[p];i;i=e[i].next)
if(e[i].to!=fa[p]&&e[i].to!=son[p])
dfs2(e[i].to,e[i].to);
return ;
}
int query(int x,int y)//核心代码
{
while(top[x]!=top[y])//判断是否在一条重链上
if(depth[top[x]]>=depth[top[y]])//链顶深度大的往上跳
x=fa[top[x]];
else
y=fa[top[y]];
return depth[x]<depth[y]?x:y;//返回深度小的节点编号
}
void add(int u,int v)//加边
{
e[++cnt].from=u;
e[cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
e[++cnt].from=v;
e[cnt].to=u;
e[cnt].next=head[v];
head[v]=cnt;
return ;
}
int main(void)//主函数
{
n=read(),opt=read(),root=read();
for(int i=1;i<n;i++)
add(read(),read());
dfs1(root,0,root);
dfs2(root,root);
while(opt--)
printf("%d\n",query(read(),read()));
return 0;
}