【学习笔记】长链剖分
概述#
在常规树链剖分中把重儿子设成 最大的儿子,这样从根跳重链时子树大小至少减半,因此只需要 次即可到达任何节点。
考虑把关键字由 改成子树内最大的深度 ,这样的剖分方法称为长链剖分。
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;
}
}
长链剖分具有一些性质:
-
从一个节点跳向链顶的父亲,所在的链长度一定增加,证明显然。
-
从一个节点跳链顶, 次可以到达树根,结合上一性质可证,最坏情况是 。但这个性质不常用。
优化 DP#
大致思想#
当 DP 的状态形如 ,只与子树 和子树内到 的距离 有关时,可以考虑长链剖分优化至 。
具体方法类似树上启发式合并,每次继承重儿子信息,轻儿子暴力合并。
复杂度证明:短链向长链合并时,长链长度一定不会增加,因此相当于把短链上的信息直接删去了。而一条链只会合并一次,每个节点只出现在一条链上,因此合并复杂度是 的。
具体实现#
难点在于如何继承重儿子的信息。
在绝大多数题目中,是一个 的过程,也就是由父亲到儿子,距离增加 。可以使用指针实现,先 DFS 预处理,对于根和每个轻儿子开子树内最大距离大小的空间,对于重儿子将指针设为父亲的指针位置加 。
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);
}
}
在一般情况下,每个数组大小就是 。
也可以用 vector
实现,但是每次加入距离更小的,实际上是倒着存储,需要特判一些边界等等,且常数较大,不推荐。
在正式 DP 时,要利用好只能枚举短链上节点到 的距离。对每棵子树单独计数的题目,直接继承即可,且对于最值或求和之类的题目,会影响到的只有短链的范畴;对于多棵子树合并计数的题目,注意到一定有短链存在,因此还是以枚举短链上距离为依托。
例题#
CodeForces-1009F Dominant Indices *2300#
朴素的统计子树内距离对应节点个数。
重儿子可以直接继承,轻儿子暴力合并即可。
Luogu-P5904 POI 2014 HOT-Hotels 加强版#
这类的三元组有两种: 是或不是重心的。
先考虑朴素 DP 怎么做,我们希望问题在子树内解决,所以要在 处统计答案。
对于第一种情况,设 为 子树内距离为 的节点个数, 为 子树内到 距离均为 且 为 的点对数。
枚举每棵子树以及距离,统计答案的过程:
对于第二种情况,设目标三元组 ,有三部分组成: 到 距离相等,均为 , 到 的距离 与到 的距离 无边集交且满足 。
因为在 处统计答案,发现 ,因此 相等的位置本质没有区别,设 为 子树内满足这样情况且 的点对数,那么转移也类似上面了。
考虑怎么优化。
是只对一棵子树有效的,不需要继承, 比较朴素的继承即可,而 比较不同,考虑到 到 后, 不变而 增加 ,因此继承是形如 ,与正常的继承不同。这就需要我们预处理指针时,把重儿子的指针设为父亲的指针减 ,从而空间要开 倍。
统计答案也需要精细处理,一个简单的想法是先计算轻子树之间的答案,这部分照搬暴力即可,同时要用一个临时数组记录一下当前统计到的 。计算完之后,可以和重儿子继承来的 再合并得到另一部分的答案。
Luogu-P3899 湖南集训 更为厉害#
容易发现 在一条链上,在 处计算答案,讨论 的位置。
如果 在 的上方,那么 相当于独立,方案数是 。
如果 在 的下方,那么 的个数实际上与 到 的距离有关,具体是 ,即 路径上点的个数和可以取的点的个数的较小值。
第一种情况可以随便计算,第二种情况是要求一个加权和,因此我们考虑如何对于每个子树内距离 求 。朴素设 为个数, 为加权和,继承时 正常,由于 的增加, 实际上是由 贡献来的,这样不能直接继承。
尝试把 拆成可以直接继承的类型,那就改成 ,这样对于不同的 ,加权和变成了 ,后者系数对于 而言是常量,前者在 到 的过程中系数不发生改变,可以直接继承。
容易发现可以后缀和优化。
其实第二种情况可以选择对每个 计数而不是对每个 计数,这样就是求深度范围内的所有节点子树大小,主席树二维数点。
总结#
长链剖分优化 DP 大致有以下技巧:
-
答案由两棵子树贡献得到,先暴力计算轻子树之间的,再统一算所有轻子树和重子树之间的。
-
在需要求和优化时,采用后缀和而不是前缀和,因为前者不需要修改长链上深度较大节点的值,保证了复杂度。
-
修改状态定义为便于继承的结果,多数采用了 的方法。
参考资料#
-
OI Wiki
作者:SoyTony
出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Long_Chain_Tree_Decomposition.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效