1. 引言
最近学了树链剖分基本思想,然后自己实现了遍代码,过了树链剖分求LCA,本文对树链剖分基本过程进行阐述,提出自己的看法,欢迎交流。
2.基本思想
顾名思义,树链剖分就是将树剖分成一条一条的链,之后快速的操作链。
树链剖分的复杂度为,预处理两遍dfs,O(n)复杂度;之后查询或者修改操作每一条链为O(log(n)),因为剖分出来的链一共有log(n)条,当然如果在链上再套个线段树,那每次操作就是O(log2(n))。之后,下文只利用树链剖分求树上两点的最近公共最先,不涉及线段树,了解了基本过程,我相信,再套个线段树也不是难事。
3.如何剖分
目前,树链剖分的有以下几种:重链剖分O(log(n))、长链剖分O(
n
\sqrt{n}
n)和实链剖分(LCT)。重链剖分用的最多,复杂度也较好,也就是链的条数,下面着重叙述什么是重链剖分。
“重”的定义:子树结点个数。于是,得出了重儿子的定义:几个儿子结点中,子树结点数量最多的,那个儿子就是重儿子。有点抽象,看图:
图中红色的点代表重儿子(当然1号根结点,我也标成了红色),以1号结点为例,他的儿子有2、8、11,2号子树结点有{2、3、4、7、5、6},8号子树结点有{8、9、10},11号子树结点有{11、12、13、14},可以得出2号子树结点数量最多,因此2号是1号结点的重儿子,为红色,8和11号是1号结点的轻儿子,为黄色。其他的结点依次类推。
重链的定义(个人见解):以轻儿子为起点(或者整个树的根结点),依次向下所有重儿子组成的链是重链,如图中红色的线,都是重链。而黄色的线为轻链,因此有轻链的定义:以重链为主干链,其分支的链为轻链,且向外扩展长度为1。
图中重链有:{1 2 4 5}、{8 9 10}、{11 13 14};轻链有{1 8}、{2 3}、{2 7}、{4 10}、{1 11 12},这里发现轻链长度有大于1的{1 11 12},如何理解扩展长度为1,我们首先以{1 2 4 5}重链向外扩展1,有{1 11},再以{11 13 14}向外扩展1,有{11 12},因此是没问题的。
从上面的分析可以看出这些性质,轻链本质就是,一些长度为1的边,将他们作为“桥梁”连接下一个重链;也可以看出,轻儿子结点,都是下一条重链的起始结点(除去某些叶子结点,因为只有一个结点,也说不上是链,图中的3、6等);
那么重点是,我们得到一系列的重链,如何对他们快速操作?答案是记录,重链的头结点,如{1 2 4 5}这条链,我们就标记
-
top[1]=1,top[2]=1,top[4]=1,top[5]=1
那么我们就能直接跳到重链的头部。那么有个问题啦,在同一条重链上可以直接跳到头部,如果不是在同一条链上呢?答案是,从这条重链的头部再往上跳,到他的父亲结点,必定在另外一条重链上,然后根据需求继续跳。如图所示,b结点跳跃的过程:
b在{6 b}这条重链,跳至6号结点,从6号再跳到{1 2 4 5 a}这条重链,再跳就是1根节点。到这里,再想想什么轻链,理解会更深刻“将他们作为“桥梁”连接下一个重链”。
4.如何求LCA
同样以上图为例,求ab两点的最近公共祖先。1、谁先跳,2、什么时候可以判断出,LCA。
答案1,深度较大的先跳,问题来了,图中a、b深度一样啊?不不不,不是直接比较a、b结点的深度,比较a和b向上跳一个链的深度,b结点所在的重链,往上,就是4号结点了,a结点所在的重链是最初的重链,往上,也只能是1号结点,我们比较1号和4号结点的深度即可,发现4号结点深度大,那么我们优先跳b结点,至4号结点。用father数组记录每个结点的父结点,即代码为 b=father[top[b]],其中top[b]=6。
答案2,如果两个点在同一重链上,那么深度较小的,为LCA,否则还是要不断的跳。a、b结点,当b结点跳到4结点时,a和4在同一重链中,即top[a]==top[4],所以4为a、b最终的LCA。
5.代码
经过上面的分析,需要这些数组:
-
son[i]=j:表示 i 结点的重儿子是 j
-
depth[i]=j:表示 i 结点的深度是 j
-
father[i]=j:表示 i 结点的父结点是 j
-
siz[i]=j:表示 i 结点的子树大小为 j
-
top[i]=j: 表示 i 结点所在的重链头部为 j
第一遍dfs得到son,depth,father,siz,第二遍dfs通过son,father得出top。第一遍dfs比较简单,重点说说第二遍dfs,首先根据son,先dfs所有的重链,遍历重链过程中,传递下去top值;再以重链为主干,遍历所有的轻链,因为轻结点会是重链的起点,因此,又可以得到新的重链,依次下去。
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false)
#define ll long long
#define maxn 500005
#define mod 99999997
#define inf 1000000007
struct edge
{
int to,next;
} G[maxn*2];
int head[maxn],num;
void add_edge(int from,int to)
{
G[++num].next=head[from];
G[num].to=to;
head[from]=num;
}
int siz[maxn];
int depth[maxn];
int son[maxn];
int top[maxn];
int father[maxn];
void dfs1(int fa,int cur,int dd)
{
father[cur]=fa;
siz[cur]=1;
depth[cur]=dd;
int heavy=0,hid=cur;
for(int i=head[cur]; i!=-1; i=G[i].next)
{
int to=G[i].to;
if(fa!=to)
{
dfs1(cur,to,dd+1);
siz[cur]+=siz[to];
if(siz[to]>heavy)
{
heavy=siz[to];
hid=to;
}
}
}
son[cur]=hid;
}
void dfs2(int cur,int tt)
{
top[cur]=tt;
if(son[cur]!=cur)
dfs2(son[cur],tt);
for(int i=head[cur];i!=-1;i=G[i].next)
{
int to=G[i].to;
if(father[cur]!=to&&to!=son[cur])
dfs2(to,to);
}
}
int Query(int u,int v)
{
while(top[u]!=top[v])
{
if(depth[father[top[u]]]<depth[father[top[v]]])
swap(u,v);
if(u==father[top[u]])//特判,为整棵树的根结点
return u;
u=father[top[u]];
}
if(depth[u]<depth[v])
return u;
else
return v;
}
void init()
{
memset(head,-1,sizeof(head));
num=0;
}
int main()
{
IOS;
init();
int n,m,s;
cin>>n>>m>>s;
for(int i=0; i<n-1; i++)
{
int x,y;
cin>>x>>y;
add_edge(x,y);
add_edge(y,x);
}
dfs1(s,s,1);
dfs2(s,s);
while(m--)
{
int u,v;
cin>>u>>v;
cout<<Query(u,v)<<"\n";
}
return 0;
}
/*
6 100
1 11
1 8
1 2
2 7
2 4
2 3
4 6
4 5
5 15
6 16
8 9
9 10
11 13
11 12
13 14
*/