长链剖分学习笔记
前置技能
学这个之前应该要比较熟悉重链剖分,推荐一下这篇博客。
一些性质
我们类比重链剖分,定义每个点所有儿子中,子树深度最大的点为它的重儿子,那么整棵树就被划分成了一些不相交的重链,然后首先就有一个性质那就是所有重链长度和是\(O(n)\)级别的,这个东西很显然,还有一个性质就是一个点的k级祖先所在重链长度一定大于等于k,这个东西考虑反证法,假设小于k,那么当前点显然会和这个点在一条重链上,然后就没了。
具体应用
求k级祖先
用长链剖分可以在\(O(\text{n log n})-O(1)\)的时间内在线回答一个点的k级祖先,首先我们预处理出每个点的第\(2^i\)个祖先,以及每一条重链的链顶向上的链长个祖先与向下链长个重儿子分别是什么,还有每个数二进制最高位highbit是哪一位,预处理这些东西的复杂度是\(O(\text{n log n})\)的,瓶颈在预处理每个点的第\(2^i\)个祖先上。那么对于询问x点的k级祖先,我们先跳到x的第\(2^{\text{highbit(k)}}\)个父亲上,那么这个时候当前点到离k级祖先的距离是小于\(\frac{k}{2}\)的,我们再跳到当前链的链顶,判断k级祖先在当前点上面还是下面即可,根据性质二,这条长链长度是大于等于\(\frac{k}{2}\)的,这样就可以\(O(1)\)回答询问了。
1.例题 lxhgww的奇思妙想,这个是模板题就不讲解了。
优化以深度为下标的DP
这个思想有点像启发式合并,大概是直接继承重儿子的dp值,然后轻儿子暴力合并,因为总链长是\(O(n)\)的,所以转移的复杂度就是\(O(n)\)的,一般来说我们可以利用数组指针来完成这个操作,把重儿子的数组指针设为当前点的$\pm$1,给剩下的轻儿子分配剩余的内存就好了。
1.例题 [POI2014]Hotel加强版,题意是统计有多少种方案可以从树上选出3个点使得它们两两间距离相等。
我们首先来考虑如何统计点,有两种情况,一种是都在一个点的子树内,第二种是两个点在子树内,另一个点在子树外,我们分开来讨论这两种情况,设\(f[x][j]\)表示x子树内距离\(x\)为\(j\)的点数,\(g[x][j]\)为\(x\)子树内有多少对点\((u,v)\)满足\(dis(u,x)=dis(v,x)=d\),且\(dis(u,v)=d+j\),那么我们有以下转移:
那么我们把每条边边权都减去\(mid\),那么只需要存在一条长度和大于等于\(0\)的路径即可,我们利用:$$dis(x,y)=dep(x)+dep(y)-dep(lca(x,y))*2$$
在这里\(dep(x)\)是从根节点到x点的路径权值和。
那么我们只要在\(lca\)处更新答案就好了,我们记\(f[i][j]\)为在\(i\)号点子树内与\(i\)号点距离为\(j\)的点的dep最大值,这样子直接做是\(O(n^3\text{log n})\)的,我们把这个值放到线段树里面那么就把一个\(n\)优化成了\(\log\),再用长链剖分每次直接继承重儿子的值又优化了一个\(n\),那么复杂度就到了\(O(\text{n log}^2\text{n})\),在这里我们用的不是数组的偏移,而是DFS序来实现对重儿子的继承,因为一条重链的\(dfs\)序是连续的,我们定义\(g[id[i]+j]\)在\(i\)号点子树内与\(i\)号点距离为\(j\)的点的dep最大值,每次更新的时候同时在线段树上更新就好了,和之前那个题一样的,要注意转移的顺序应该是先更新答案再更新线段树里的值,代码戳我。
总结
其实用的比较多的还是优化DP,这类DP特征也很明显就是以深度为下标,做题的时候注意一下就好了,如果不是以深度为下标的话用启发式合并也可以做到很优秀的一个\(\log\)的复杂度,其实最重要的还是推和树上距离有关的转移方程,只要方程能写出来优化也都是比较套路的东西了。