长链剖分

我们现在有一棵有根树(节点的深度定义为根到它的距离)。
设节点 \(u\) 的所有儿子中,深度最大的点为它的长儿子,记作 \(son_u\)。(存在多个则任取一个,没有儿子则为空)
记每个节点到它的长儿子的变为长边,其余边为短边。
一段极长的全部由长边组成的链称为长链。特别的,按此过程划分后,不在长链上的点(都是叶子节点)也各算作一条长链。
显然,每个点在且仅在一条长链上。

长链剖分有两个经典应用,分别为 \(O(nlogn)\sim O(1)\) 求树上 \(k\) 级祖先和优化某维度与深度相关的 dp(目前于我而言,此处深度特指向叶向走的距离)
第一个应用因为一般效率不如 \(O(n)\sim O(logn)\) 的轻重链剖分,并没有什么用,略去。
下面是优化 dp 的过程。
例:P5904
由原题题解所述状态,发现第二维与深度相关,可以考虑长链剖分优化。
优化后的过程为:先对 \(son_u\) 进行 dp,并由 \(u\) 直接继承。然后再照常合并别的儿子。(要注意循环范围,以及继承以后先算上 \(g_{u,0}\)
示例(先给 \(u\) 分配了空间,所以这么写):

 int *f[M],*g[M];
 f[son[u]]=f[u]+1,g[son[u]]=g[u]-1;

能否优化的关键在于当前点能否做到 \(O(1)\) 继承长儿子的结果。
观察转移方程:

\[g_{u,j+1}\leftarrow g_{u,j+1}+f_{v,j}*f_{u,j+1} \]

\[g_{u,j-1}\leftarrow g_{u,j-1}+g_{v,j} \]

\[f_{u,j+1}\leftarrow f_{u,j+1}+f_{v,j} \]

发现对于我们计算的第一个儿子( \(son_u\) ),\(f\)\(g\) 的计算都只是将儿子的数组进行了平移。因此能够 \(O(1)\) 继承。
注意到一个关键的细节。
\(f_{son_u}\) 是将 \(f_u\) 的数组向右平移一位,并扔掉最后一位。(即 \(f_{u,1}\) 对应 \(f_{son_u,0}\)
\(g_{son_u}\) 是将 \(g_u\) 的数组向左平移一位,并扔掉最后一位。(即 \(g_{u,-1}\) 对应 \(f_{son_u,0}\)
所以若将 \(f_u\)\(g_u\) 连续分配的话,\(f\)\(g\) 之间要额外留一倍空间。
综上,通过长链剖分解决本题。


显然,\(O(\sum{链长})=O(n)\),因为每条边至多属于一条长链。
上述算法的空间复杂度为 \(O(n)\),因为我们只在链顶分配这条长链的空间,分配的总空间为 \(3*\sum{链长}\)
上述算法的时间复杂度也是 \(O(n)\),因为只在链顶处暴力继承一条链的信息,复杂度为 \(O(n)\)\(O(1)\) 继承长儿子和 \(O(\sum{链长})\) 的和。
代码可以参考 @xht 的题解

posted @ 2024-05-04 22:47  studentDL  阅读(41)  评论(0)    收藏  举报