树链剖分 入门(求LCA)

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
*/



  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值