算法简介:
前段时间为了一道LCA的题目伤透了脑筋,一直都只过了8组数据,另外两组数据总是超时。因为题目的特性,在这里用tarjan离线的方法求LCA并不是个明智的选择反而会使问题变得更加复杂。于是我就在网上自学了一下树链剖分的LCA求法,主要思想是利用轻重链的划分来快速在树上进行跳转。
算法分析:
1.按照深度优先遍历一遍整棵树,对于每个结点,选取它的子节点中以这个子节点为根的子树
s
i
z
e
size
size最大的那一个,将这个子节点和他的父节点连接起来,表示他们在同一条重链上。
2.在查询两个节点的LCA时,如果当前两个节点不在同一重链上,则进行跳转(先跳深度更大的那个)。
3.重复上面操作
2
2
2直到两个节点处于同一重链,这时候因为两点在同一条链上,则返回深度更低的那一个,即两个点的LCA。
模板程序:
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN=300000;
int n,m,t=0;
int p[MAXN],d[MAXN],size[MAXN],son[MAXN],top[MAXN],fa[MAXN];
struct node
{
int v;
int next;
}e[MAXN*2];
void insert(int u,int v)
{
e[t].v=v;
e[t].next=p[u];
p[u]=t++;
}
void dfs1(int x)
{
size[x]=1;
for(int i=p[x];i!=-1;i=e[i].next)
{
int v=e[i].v;
if(fa[x]!=v)
{
fa[v]=x;
d[v]=d[x]+1;
dfs1(v);
size[x]+=size[v];
if(size[son[x]]<size[v])//son[i]表示结点i的儿子中以其为根的子树size最大的那个(用来构造重链)
son[x]=v;
}
}
}
void dfs2(int x)
{
if(x==son[fa[x]])//如果x是fa[x]的儿子中拥有最大size的那个
top[x]=top[fa[x]];//把top[x]连向top[fa[x]](形成重链)
else
top[x]=x;//否则指向它自己
for(int i=p[x];i!=-1;i=e[i].next)
if(e[i].v!=fa[x])
dfs2(e[i].v);
}
int LCA(int u,int v)
{
while(top[u]!=top[v])//u、v不在同一条重链上时
{
if(d[top[u]]>d[top[v]])//将深度大的上提
u=fa[top[u]];
else
v=fa[top[v]];
}
if(d[u]<d[v])//返回u、v中在较上方的那个
return u;
return v;
}
int main()
{
memset(p,-1,sizeof(p));
scanf("%d%d",&n,&m);
int root;
bool isNotRoot[MAXN];
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
isNotRoot[v]=1;
insert(u,v);
insert(v,u);
}
for(root=1;root<=n;root++)
if(!isNotRoot[root])
break;
d[root]=1;
dfs1(root);//预处理d和fa
dfs2(root);//预处理top
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",LCA(u,v));
}
return 0;
}
如果大家理解了的话就可以去做几道题练一下手啦!下面推荐几道题:
BZOJ 1832,3612,NOIP2015 运输计划