【学习笔记】树链剖分

1.树上差分

点差:P3128 Max Flow P

边差:P3038 Grass Planting G

对于普通的序列差分,最后进行一次 difi=difi+difi1 的操作使得所有点变回原来的样子。

但是树上差分不能这么干。因为我们进行普通差分时会让 difr+1difr+11。而在树上一个点可能有许多儿子,全部进行一次操作代码就很难搞,而且会被菊花图卡。

分析一下出问题的原因出在树上一个点可以有多个儿子。从而我们会想到在树上一个点还是只有一个祖先。于是我们再想到树上非常容易出现的递归性,就能想到最后进行的应该是 difi=dife(i  e )

所以对于 difidifi+1。根据最后的求值,我们可以理解为从根到 i 的所有点加上 1。而我们只要从 ei 的所有点加上 1。于是我们让 diffa(e)diffa(e)1

return;

对于边差分就考虑把边下放到点。对于一个边 (x,y),应该下放到深度更深的点,因为这个点只有一个父节点,从而代表只有这一条边连向了深度更浅的点。

那么这样的两条链应该拆成 (l,x)(l,y)。操作就好。

注意点差分边差分修改的位置不同。

2.树上启发式合并(dsu on tree)

当题目中没有修改并且查询仅与子树有关可以考虑通过树上启发式合并来做到 O(nlogn)

定义重儿子为子树中节点最多的儿子。轻儿子是除重儿子外的儿子。一个点连向重儿子的边叫做重边,其他的叫做轻边。

考虑这样一个暴力:每次把一个子树的答案算出来,然后清空所有数组。显然复杂度是 O(n2) 的。

考虑优化这个暴力。我们发现对于子树 x,最后一个计算的儿子的贡献不需要清空,因为自己反正还要用一遍。

那么我们可以考虑把最大的儿子(重儿子)留下来不删除。也就是在计算当前点的时候,先把轻儿子答案计算完,并且计算完就删掉防止和其他儿子产生冲突,然后计算重儿子答案并保留贡献,再把所有轻儿子贡献加回来得到当前答案。

实现的话我们给函数多加上一个布尔值代表这一次的数据需不需要保留。然后后面给轻儿子加贡献就写一个新的函数。

复杂度是 O(nlogn)

  • 证明:

考虑一个点会被计算的次数。如果它位于 f 的重儿子中它的贡献会被传上去,那么一直是统计一遍。然后如果碰到轻边会重新计算一次,也就是说一个点被计算的次数是它到根的轻边数加一。

考虑一条边是轻边,那么节点数至少乘以 2,所以轻边数是 logn 的。所以复杂度 O(nlogn)

3.重链剖分

重链是连续的重边连成的链。

根据上面的性质我们知道重链的断开点是轻边,一共只有 logn 个轻边,所以重链数量级是 logn 的,而且大多数情况下搞不满。

根据这个性质,对于重链剖分,它能维护很多东西。前提是 dfs 序,在 dfs 时优先访问重儿子能够保证重链 dfs 序连续,然后子树 dfs 序也连续,所以它能配合很多可爱的数据结构进行很多可爱的算法。

针对子树的操作很简单,对于路径操作,我们维护当前点所在的重链顶,然后找两个端点里面深度最深的点往上面跳直到位于同一个重链(具体原因忘了,有博客讲过),过程中根据数据结构更新。

4.长链剖分

类似重链剖分。把子树中深度最大的点作为重儿子。一个点到根节点的重边切换次数是 O(n) 级别的。不会证,反正长剖用处不大。

可以 O(nlogn)O(1)k 级祖先。做法挺好玩的,自己看题解,但是常数过大不如重剖。

最大的用处在于优化 某些 dp,但是由于涉及指针,写起来非常难受。

针对链接那道题记录一下一般长剖优化 dp 的做法吧。

快进到 fi,j 表示子树 i 中离 i 距离为 j 的点的个数,gi,j 表示子树 i 中合法的无序点对 (x,y) 个数,满足令 c=lca(x,y),有 dis(x,c)=dis(y,c)=dis(i,c)+j,即 ic 补上一个长度为 j 的链后有 (i,x,y) 为合法三元组。

对于节点 p 的贡献:

  • ansans+gi,0+x,yson(p),xyfx,j1×gy,j+1

  • gp,j=xson(p)gp,j+1+x,yson(p),xyfx,j1×gy,j1

  • fp,j=xson(p)fx,j1

注意到其他维度只跟深度有关,可以考虑长剖优化。

对于重儿子发现直接继承祖先的 f,g 即可,具体地,若 q 的父亲是 q,则 fq 即为 fp 左移一位,gq 即为 fq 右移一位。这里用指针实现,将 dp 值存在一个数组然后用指针访问得到左移右移的继承效果。轻儿子暴力转移,加上一个前缀和可以做到线性。

如果没有 g 数组而仅考虑 f 数组,深度最大为长链长度 d,于是只需要申请 d 的空间,而这之后长链上的点都会继承这个空间,所以总空间开销为点数 n。加上 g 数组后由于左移需要给 g 左边预留 d 的空间,g 自身也有 d 的大小,空间开销变成 3n 左右。注意边界的处理,fp,d,gp,d 都为 0,我们可以在取这里的时候特判或者多预留一点空间,防止某些地方的 f,g 重叠影响答案。

观察转移发现一个点的时间开销是其轻儿子长链长度和,这个东西和空间复杂度基本相同,所以时间均摊下来是 O(n) 的。

posted @   Wind_Leaves_ShaDow  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示