[]树链剖分

写点东西记录一下,起码让自己记得自己曾经对树链剖分挺有兴趣:)

首先来看一道题:[BZOJ1036]树的统计

最暴力的做法就是直接修改,然后查询时两个节点往上跳到lca

这样做会很慢,因为它是O(树高)的,随便出个链的数据就能卡掉

所以我们想要每次往上跳很多层,这样跑起来会快一些

也就是顺着“树链”往上跳

[]树链剖分

 

我们询问$14\cdots10$的路径,只需跳$(14\cdots12)+[12\cdots9]+(9\cdots1)+(10\cdots3)+[3\cdots1]$

树链剖分不是一种具体的算法,而是一种思想

它把整棵树剖分成许多链,并且用数据结构(比如线段树)维护每一条链

于是(往上跳很多层)=(数据结构的区间操作)

既然要顺着链往上跳,我们应该先把树剖分成一些链

常用的方法是"重链剖分"

把一个节点的(子树大小最大的儿子)称为"重儿子",其余儿子称为"轻儿子"

一个节点向它重儿子连的边称为"重边",其余边称为"轻边"

如果当前节点为$x$,边$(fa[x],x)$为轻边,那么显然$size[fa[x]]\geq2\cdot size[x]$

所以从某个节点向上跳最多经过$O(log_2n)$条轻边

往上跳的过程中,由于轻边和重链交替出现,所以经过重链的数量级也是$O(log_2n)$的

挺快的,是吧?

剖分的方法也很简单,两次dfs就可以了

第一次记录深度$dep$,子树大小$siz$,重儿子$son$,父亲$fa$

第二次记录节点在数据结构中的位置$pos$,所属重链的顶端$bl$

dfs完建立数据结构,一般常用线段树或树状数组

查询/修改路径时顺着重链向上跳的同时在数据结构内做区间操作

具体点,查询/修改路径$x$到$y$

让$dep[bl[x]]\geq dep[bl[y]]$(深度大的先往上跳)

对区间$[pos[bl[x]],pos[x]]$操作(这条重链)

让$x=fa[bl[x]]$(同时跳过一条重链和一条轻边)

重复上述过程直到$bl[x]=bl[y]$(在同一条重链上)

最后对区间$[pos[x],pos[y]]$操作(一条重链上的剩余部分)

于是我们就可以愉快地解决路径的问题了w

子树呢?

观察第二个dfs,我们发现一个子树内的节点在数据结构上的位置都是连续的

所以对以$x$为根的子树操作就直接在数据结构内对$[pos[x],pos[x]+siz[x]-1]$就可以了

于是我们也可以愉快地解决子树的问题了w

就是这样喵

最后的一点小补充:

因为每次找$O(log_2n)$条重链

在重链上用数据结构进行操作的时间复杂度是$O(log_2n)$的

所以......你懂的,树链剖分每次操作的时间复杂度是$O(log_2^2n)$

但其实其中的一个$log$是上界,一般达不到

所以一般情况下没有出题人会丧心病狂到出数据卡这个$log_2^2n$,所以把它当$log_2n$用就好