【学习笔记】长链剖分

Page Views Count

概述#

在常规树链剖分中把重儿子设成 siz 最大的儿子,这样从根跳重链时子树大小至少减半,因此只需要 O(logn) 次即可到达任何节点。

考虑把关键字由 siz 改成子树内最大的深度 dep,这样的剖分方法称为长链剖分。

void dfs1(int u,int fa,int d){
    dep[u]=d,mxdep[u]=dep[u];
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==fa) continue;
        dfs1(v,u,d+1);
        if(mxdep[v]>mxdep[u]) mxdep[u]=mxdep[v],son[u]=v;
    }
}

长链剖分具有一些性质:

  • 从一个节点跳向链顶的父亲,所在的链长度一定增加,证明显然。

  • 从一个节点跳链顶,O(n) 次可以到达树根,结合上一性质可证,最坏情况是 1+2++n。但这个性质不常用。

优化 DP#

大致思想#

当 DP 的状态形如 fu,d,只与子树 u 和子树内到 u 的距离 d 有关时,可以考虑长链剖分优化至 O(n)

具体方法类似树上启发式合并,每次继承重儿子信息,轻儿子暴力合并。

复杂度证明:短链向长链合并时,长链长度一定不会增加,因此相当于把短链上的信息直接删去了。而一条链只会合并一次,每个节点只出现在一条链上,因此合并复杂度是 O(n) 的。

具体实现#

难点在于如何继承重儿子的信息。

在绝大多数题目中,是一个 fv,dfu,d+1 的过程,也就是由父亲到儿子,距离增加 1。可以使用指针实现,先 DFS 预处理,对于根和每个轻儿子开子树内最大距离大小的空间,对于重儿子将指针设为父亲的指针位置加 1

void dfs2(int u,int f){
    if(u==1){
        dp[u]=p,p+=mxdep[u]-dep[u]+1;
    }
    if(!son[u]) return;
    dp[son[u]]=dp[u]+1;
    dfs2(son[u],u);
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==f||v==son[u]) continue;
        dp[v]=p,p+=mxdep[v]-dep[v]+1; 
        dfs2(v,u);
    }
}

在一般情况下,每个数组大小就是 n

也可以用 vector 实现,但是每次加入距离更小的,实际上是倒着存储,需要特判一些边界等等,且常数较大,不推荐。

在正式 DP 时,要利用好只能枚举短链上节点到 u 的距离。对每棵子树单独计数的题目,直接继承即可,且对于最值或求和之类的题目,会影响到的只有短链的范畴;对于多棵子树合并计数的题目,注意到一定有短链存在,因此还是以枚举短链上距离为依托。

例题#

CodeForces-1009F Dominant Indices *2300#

朴素的统计子树内距离对应节点个数。

重儿子可以直接继承,轻儿子暴力合并即可。

Luogu-P5904 POI 2014 HOT-Hotels 加强版#

这类的三元组有两种:LCA 是或不是重心的。

先考虑朴素 DP 怎么做,我们希望问题在子树内解决,所以要在 LCA 处统计答案。

对于第一种情况,设 fu,du 子树内距离为 d 的节点个数,gu,du 子树内到 u 距离均为 dLCAu 的点对数。

枚举每棵子树以及距离,统计答案的过程:

gu,d+1×fv,dans

fu,d+1×fv,dgu,d+1

fv,dfu,d+1

对于第二种情况,设目标三元组 (u,v,w),有三部分组成:LCA(u,v)u,v 距离相等,均为 d1LCA(u,v,w)w 的距离 d2 与到 LCA(u,v) 的距离 d3 无边集交且满足 d1=d2+d3

因为在 LCA(u,v,w) 处统计答案,发现 d3=d1d2,因此 d1d2 相等的位置本质没有区别,设 hu,du 子树内满足这样情况且 d1d2=d 的点对数,那么转移也类似上面了。

考虑怎么优化。

g 是只对一棵子树有效的,不需要继承,f 比较朴素的继承即可,而 h 比较不同,考虑到 vu 后,d1 不变而 d2 增加 1,因此继承是形如 hv,dhu,d1,与正常的继承不同。这就需要我们预处理指针时,把重儿子的指针设为父亲的指针减 1,从而空间要开 2 倍。

统计答案也需要精细处理,一个简单的想法是先计算轻子树之间的答案,这部分照搬暴力即可,同时要用一个临时数组记录一下当前统计到的 f,g,h。计算完之后,可以和重儿子继承来的 f,h 再合并得到另一部分的答案。

Luogu-P3899 湖南集训 更为厉害#

容易发现 a,b,c 在一条链上,在 a 处计算答案,讨论 b 的位置。

如果 ba 的上方,那么 b,c 相当于独立,方案数是 max(depa,k)×(siza1)

如果 ba 的下方,那么 b 的个数实际上与 ca 的距离有关,具体是 cson(a)min(depcdepa1,k),即 (a,c) 路径上点的个数和可以取的点的个数的较小值。

第一种情况可以随便计算,第二种情况是要求一个加权和,因此我们考虑如何对于每个子树内距离 dd1。朴素设 fu,d 为个数,gu,d 为加权和,继承时 f 正常,由于 d 的增加,gu,d+1 实际上是由 dfv,d=(d1)fv,d+fv,d=gv,d+fv,d 贡献来的,这样不能直接继承。

尝试把 d 拆成可以直接继承的类型,那就改成 depvdepu,这样对于不同的 u,加权和变成了 depv×fu,ddepu×fu,d,后者系数对于 u 而言是常量,前者在 vu 的过程中系数不发生改变,可以直接继承。

容易发现可以后缀和优化。

其实第二种情况可以选择对每个 b 计数而不是对每个 c 计数,这样就是求深度范围内的所有节点子树大小,主席树二维数点。

总结#

长链剖分优化 DP 大致有以下技巧:

  • 答案由两棵子树贡献得到,先暴力计算轻子树之间的,再统一算所有轻子树和重子树之间的。

  • 在需要求和优化时,采用后缀和而不是前缀和,因为前者不需要修改长链上深度较大节点的值,保证了复杂度。

  • 修改状态定义为便于继承的结果,多数采用了 d=depvdepu 的方法。

参考资料#

作者:SoyTony

出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Long_Chain_Tree_Decomposition.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   SoyTony  阅读(163)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示