长链剖分是另一种树的剖分方式。其主要用途之一是加速以深度为下标的 dp 到 $O(n)$。
长链剖分过程
类比重链剖分,我们定义树上一个节点的向下深度 $d_u$ 为从这个点出发向下走最大的距离。可以用树形 dp 理解,即 $d_{\mathrm{leaf}}=1$,$d_u=\max\{d_v+1\}$。
同时如果一个点有儿子,我们就定义一个点的长儿子为 $v$ 中 $d_v$ 最大的点,相应的剩余的儿子就是短儿子。定义长链就是一个短点和它的长儿子链成的从上往下的链。
从这个定义中不难发现长链的一些性质:
- 所有长链的长度和为 $O(n)$。
- 任意一个节点 $u$ 的 $k$ 级祖先 $f$ 所在长链的长度不小于 $k$。如果小于 $k$,那么显然 $f-u$ 这条链比原长链更长,又因为长链是从一个节点向下的最长链,因此矛盾。
- 从一个节点出发,不断从跳到当前长链所在链顶的父亲,一直跳到根节点,跳链次数为 $O(\sqrt n)$。如果一个节点 $u$ 从一条长链跳到了另外一条长链上,那么跳跃到的这条长链的长度不会小于之前的长链长度。最坏情况下,链长分别为 $1,2,⋯,n$,也就是最多跳跃 $\sqrt n$ 次。
如何构造数据卡满跳链次数
我们可以构造这么一棵二叉树 $\mathcal T$:
假设构造的二叉树参数为 $d$。若 $d\neq 0$, 则在左儿子构造一颗参数为 $d-1$ 的二叉树,在右儿子构造一个长度为 $2d-1$ 的链;若 $d=0$, 则我们可以直接构造一个单独叶节点,并且结束调用。
这样子构造一定可以将单独叶节点到根的路径全部为轻边且需要 $d^2$ 级别的节点数。
取 $d=\sqrt n$ 即可。
这样的性质说明在处理重链剖分处理的需要跳链的问题上,长链剖分复杂度一般劣于重链剖分。但是,在处理与深度相关的问题时,长链剖分有时却能取得优秀的复杂度。
例子:树上 $k$ 级祖先
给定一棵树和 $m$ 次询问,每次询问给出一个点 $u$ 和一个参数 $k$,你需要求出 $u$ 的 $k$ 级祖先。$n\le5\times10^5$,$m\le5\times10^6$。
算法 $1$
利用倍增算法暴力向上跳即可,复杂度 $O((n+m)\log n)$。
算法 $2$
利用长链剖分,向上跳,复杂度 $O(n+m\sqrt n)$。
算法 $3$
我们考虑将上面两种算法结合起来。
首先我们记 $t=\lfloor\log_2k\rfloor$,并且直接让 $u$ 向上跳 $2^t$ 级。记 $f$ 为 $u$ 的 $2^t$ 级祖先,那么由上面的性质可知 $f$ 所在长链长度 $d$ 一定大于等于 $2^t$。由不等式知 $k-2^t<2^t\le d$。因此如果我们处理出 $f$ 所在链头 $T$ 向上向下走 $1\sim d$ 次的祖先儿子,我们就可以通过先跳到 $T$ 再查表跳点的方式跳到 $u$ 的第 $k$ 级祖先。
预处理的时候需要处理每个链头的 $2d$ 的信息,这样的时间复杂度是 $O(\sum2d)=O(n)$,因为长链总长度为 $O(n)$。
因此总时间复杂度是 $O(n\log n+m)$,可以通过。
长链剖分优化 dp
一些 dp 方程中,常常会出现与深度相关的复杂度,一般来说复杂度常常是 $O(\sum i\cdot dep_i)=O(n^2)$ 的,而利用长链剖分来优化复杂度就可以少掉一个 $n$。
长链剖分优化的思想与树上启发式合并非常相似。树上启发式合并善于解决与子树大小相关的问题,可以将 $O(n^2)$ 的算法优化到 $O(n\log n)$。
这里的思想也是类似的。我们先计算出 $u$ 的长儿子的 dp 数组,然后不清空、直接将其贡献合并到 $u$ 上;然后枚举短儿子并求出答案,并直接加起来。
这样的时间复杂度怎么求?注意到暴力合并短儿子的时间复杂度是所有短儿子长链的长度和,也就是 $O(n)$ 的。
一般来说实现上要使用指针以方便地实现合并贡献这一步(其实就是直接拿走长儿子的 dp 数组)。
CF1009F
给定一棵以 $1$ 为根的树,生成一个 $n\times n$ 的矩阵 $D$,其中 $D_{i,j}$ 表示 $i$ 子树中距离 $i$ 为 $j$ 的点数。对于所有 $i$ 求 $j$ 使得 $D_{i,j}=\max\limits_{0\le k<n}D_{i,k}$ 且 $j$ 最小。$n\le10^6$。
考虑暴力 dp,设 $dp_{u,i}$ 表示从 $u$ 向下的深度累计数组,那么
发现这东西下标和深度有关,因此套上长链剖分优化的板子,就变成了 $O(n\log n)$。
1 | int n,fa[N],ans[N],dep[N],son[N]; |
P5904
给定一棵树,边长为 $1$,统计满足以下条件的无序数对 $(x,y,z)$ 的组数:
- $1\le x,y,z\le n$;
- $\operatorname{dis}(x,y)=\operatorname{dis}(x,z)=\operatorname{dis}(y,z)$。
非常抽象的 dp。令 $f_{u,i}$ 表示从 $u$ 向下走 $i$ 步的节点数量,$g_{u,i}$ 表示
其中 $\mathrm{lca}$ 是 $x,y$ 的最近公共祖先。
那么可以求解 dp 转移方程:
答案就是
很显然可以通过前缀和优化、或者通过点按照顺序合并到父亲上的方式将其优化到 $O(dep)$ 转移、$O(n)$ 求解答案。
注意到转移方程和下标有关,因此上长链剖分即可。注意空间要开大约 $4$ 倍,因为 $g$ 的转移中下标一维是加而不是减,这要求我们向前分配空间,因此 $g_u$ 对应的指针前后都要留出 $d_u$ 的长度。
1 | ll *f[N],*g[N],p[N<<2],*ptr=p,ans=0; |
P3899
定义一棵以 $1$ 为根的树上两个不同的点 $u,v$ 的两种关系:
- 若 $u$ 是 $v$ 的祖先,称 $u$ 比 $v$ 厉害。
- 若对于一个常数 $k$,$\operatorname{dis}(u,v)\le k$ 则称 $u,v$ 彼此彼此。
给定一棵树和 $q$ 次询问,每次询问给出两个整数 $u,k$,要求统计对于 $k$,有多少对有序点对 $(a,b)$ 满足:
- $u,a,b$ 两两不同,且 $u,a$ 都比 $b$ 厉害;
- $u,a$ 彼此彼此。
根据这个关系不难发现:$u,a,b$ 是树上一条直上直下的链且 $b$ 最深,$\operatorname{dis}(u,a)\le k$。
定义根节点的深度为 $1$,如果 $dep_a
我们不妨设 $dp_{u,i}$ 表示 $u$ 子树中、距离 $u$ 小于等于 $i$ 的点子树大小减 $1$ 的和,即
那么显然可得到转移方程
这里下标也是深度,利用长链剖分优化。但是注意到所有的 $dp_{v,i}$ 都要加上一个统一的常数 $siz_v-1$,无法快速更新合并。因此使用懒标记,定义 $tag_u$ 为所有 $dp_{u,i}$ 应当统一加上的值,那么就无需处理常数,只需要在合并的时候让 $tag_u\leftarrow tag_v+siz_v-1$ 即可。时间复杂度 $O(n)$。
1 | il void dfs2(int u,int fa) { |