树链剖分 学习笔记

Posted zcdhj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树链剖分 学习笔记相关的知识,希望对你有一定的参考价值。

像我这么懒肯定不会有图辣

GCZ大爷的安利下来学树剖,慢慢补吧。。。

前言

这几天边看《高级数据结构》,边看各路神犇的Blog,终于看懂一点点了。不得不说那本书作者的码风是真的垃圾。

树链剖分,是一种将树剖分成多条不相交的链的算法,并通过其他的数据结构来维护这些链上的信息。

最简单的例子就是LCA,假设现在有一棵退化成链的树。如果要求任意两点的LCA,因为他们在同一条链上的缘故,只需要判断一下两者的深度就行了。由此可见,在链上是比在树上更好操作的。

那么该怎么将一棵树剖分开来捏?

先搬出一堆概念:

  • 重儿子

在以\(X\)节点为根的子树中,节点数最多的子树的根节点,即是\(X\)节点的重儿子。不难证出,如果\(Y\)节点为\(X\)节点的重儿子,设\(size(cur)\)为以\(cur\)节点为根节点的子树的节点数,\(size(x)/2<=size(y)\)

  • 重边

连接\(X\)节点与\(X\)节点的重儿子的边,我们叫他重边。

  • 重链

一堆重边连起来的链。

  • 轻链

一堆非重边连起来的链。

对于每个节点,找出其重儿子,剖分成一条条重链与轻链,就剖分好了。

基本操作

首先先要按照前面说的,\(dfs\)一遍树,找出每个点的重儿子。

inline void dfs1(int x,int fa)
{
    depth[x]=depth[fa]+1; //x节点的深度等于其父节点的深度+1
    father[x]=fa; //标记好x节点的父亲
    siz[x]=1; //目前以x为根的子树的节点总数为1
    int maxx=-1; //用来比较重儿子用
    for(register int k=first_edge[x];k;k=edge[k].next_edge)
    {
        if(edge[k].to!=fa) 
        {
            dfs1(edge[k].to,x);
            siz[x]+=siz[edge[k].to]; //子树大小累计起来
            if(siz[edge[k].to]>maxx) //更新重儿子的信息
            {
                maxx=siz[edge[k].to];
                son[x]=edge[k].to;
            }
        }
    }
    return;
}

然后再\(dfs\)一遍树,这次要找出每条重链最上面的节点,顺便将每个点重新编号,保证一条链上的节点编号都是连续的,方便以后的维护。

inline void dfs2(int x,int topf)
{
    dfn[x]=++number; //标记一下这个点的新编号
    wt[number]=W[x]; //储存一下新的点权
    top[x]=topf; //储存一下链最上面的顶点
    if(!son[x]) return; //如果这个节点是叶子节点 直接返回
    dfs2(son[x],topf); //优先处理重儿子 这样就保证了一条链上的节点编号是连续的
    for(register int k=first_edge[x];k;k=edge[k].next_edge)
        if(edge[k].to!=father[x] && edge[k].to!=son[x]) dfs2(edge[k].to,edge[k].to); 
    return;
}

上面两步做完之后,我们要对于这些链建一棵线段树。之前保证一条链上的节点编号是连续的,就是为了方便线段树的区间操作。

//你真的以为会有线段树的代码吗 naive

现在我们引入一道模板题

操作如下:

  • 将树从\(X\)到$Y \(结点最短路径上所有节点的值都加上\)Z$
  • 求树从\(X\)\(Y\)结点最短路径上所有节点的值之和
  • 将以\(X\)为根节点的子树内所有节点值都加上\(Z\)
  • 求以\(x\)为根节点的子树内所有节点值之和

我们先来分析一下操作1。

很显然,\(X\)\(Y\)的最短路径一定会经过他们的\(LCA\)

前面也说了,如果\(X\)\(Y\)在一条链上,谁深度小谁就是LCA,那么其实就是让\(X\)\(Y\)不停的往上爬,直到爬到一条相同的链为止。然后再往上跳的过程中不断用线段树来更新就好了。

inline void update_range(int x,int y,int z)
{
    z%=mod;
    while(top[x]!=top[y]) //x和y不在一条链上
    {
        if(depth[top[x]]<depth[top[y]]) std::swap(x,y); //始终让x所在的链深度最大
        segtree.update(1,1,N,idx[top[x]],idx[x],z); //用线段树更新
        x=father[top[x]]; //跳到这条链的顶节点的父节点上面去
    }
    if(depth[x]>depth[y]) std::swap(x,y); //如果x的深度比y大 交换x和y 方便后面的操作
    segtree.update(1,1,N,idx[x],idx[y],z); //此时在一条链上了 直接更新
    return;
}

待填坑

以上是关于树链剖分 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

树链剖分—学习笔记

[学习笔记]树链剖分

树链剖分 学习笔记

树链剖分学习笔记

学习笔记::lct

树链剖分(轻重链剖分)算法笔记[转]