Ynoi
2019-08-20 11:17:06
树链剖分家族有两姐妹,一个叫重链剖分,一个叫长链剖分
我们说树剖,一般指重链剖分。而我在本博客里,会讲讲长链剖分
由于一些原因,对于重链剖分和长链剖分的相似部分本人会略过
所以看本文前,建议先学习重链剖分
长链剖分和重链剖分一样,是把一棵树分成若干条不相交的链。
但是,这里的重儿子不再是子树大小最大的,而是深度最大的子节点。(我们这里叫长儿子)
我们定义一个节点的深度,为这个这个节点的到这个节点的子树中的任意节点的路径经过节点数的最大值。
这样,我们就可以类似重链剖分的方式,把树分成了若干条不相交的链。
举个例子:
下面这棵树,
绿色的边就是长链
我们定义一个节点和它的长儿子节点相连的边(就是上图绿色的边)为实边,其他为虚边
对于一条长链
我们定义链顶为一条链顶为这条链深度最大的节点
定义链底为一条链顶为这条链深度最小的节点
剖分的代码和重链剖分类似
//dep[x] 指x的深度
//zson[x] 表示 x 的长儿子
void dfs(int x)
{
int maxdep = 0;
dep[x] = 1;
for(int i = t[x]; i != 0; i = b[i].ls)
{
int y = b[i].y;
if(y != fa[x]) {
fa[y] = x;
dfs(y);
if(dep[y] > maxdep) {
maxdep = dep[y];//感谢KonjAC_Lucas指出这个锅
zson[x] = y;
}
dep[x] = max(dep[x],dep[y]+1);
}
}
}
证明:
对于一个节点,深度为
那么如果我们跳了x条虚边,此时树的大小,最少为
当然也会有人说,可以离线下来,然后dfs遍历整棵树,在遍历到每个节点的时候存一下它到根节点路径的所有节点就可以了。时间复杂度
然而,还有更好的做法吗?
没错,就是长链剖分
长链剖分可以做到
首先我们知道一个性质
任意一个点的k级祖先所在链的链长一定大于等于k。
证明很简单,这个点k级祖先所在的链长度肯定大于等于它到这个节点。
我们先倍增出每个节点的
然后,我们对于每条链处理。如果链长是
对于每次询问
我们设
然后,我们从利用倍增数组,从
此时,我们发现当前节点所在链 链长一定大于
现在,如果这个节点的
如果在这个节点的
然后我们就可以
这里有一个模板题
在学习这个之前,可以先学习一下dsu on tree
由于长链有很好性质,我们可以用来优化一些树上的dp。
一般来说,由于长链是一个点所在子树最长的链
所以这个性质可以很方便的维护一些信息
这样讲比较抽象,我们来一个具体的例子
CF1009F
题意:
给出一棵有根树,对于每个节点
树的点数
其实就是求子树内深度的众数
首先我们想一个朴素的
这是
然后对于每个点,我们可以用该点所在的长链,向下
由于长链的性质,肯定是有地方存的
这样的话,对于每个节点的转移,长儿子的信息可以直接继承上去,其他儿子则暴力将它们所在的长链信息合并上去
由于每条链最多被合并一次
所以这样做就是
这样讲讲可能比较抽象
我们来举一个例子
点里的数字是编号
点外地数字是我们维护的信息,我们定义这个值为
则答案就是
现在除了节点1其他都已经维护好了
比如说
而2号点答案就是1
现在我们要维护
我们设置
然后,对于
对于
怎么合并?
合并到对应高度即可
在合并的时候同时维护1所在链最大值
然后1的答案就是2了
[湖南集训]谈笑风生
设
设
设
给定一棵
我们分类讨论
当
当
这个用长链剖分维护一下即可,求出与a距离i的点的贡献,维护一下前缀和即可