题解 P5903 【【模板】树上 k 级祖先】
Level Ancestor 问题的若干解法
Level Ancestor 问题(以下简称“LA 问题”)的描述如下:
对于一棵有根树
T ,给出树上的一个结点x 与一个整数k\leqslant depth(x) ,求出结点x 的第k 个祖先
已经有许多优秀的文章对这个问题的经典解法进行了清楚的解释,因此本文将把重心放在对这个问题的研究过程进行分解。我将通过从基本的暴力算法一点点向经典解法靠近并拓展的方式向大家展示研究 LA 问题的思维路径。
这篇文章只关注 LA 问题的解法是如何一步步被优化的,因此只提供算法描述,不会提供具体代码。
算法一
可以考虑直接使用动态规划算法一次性预处理出所有答案并存在一张表里,基本的暴力算法。
预处理:
算法二
考虑对算法一进行优化,牺牲一部分查询的复杂度。
使用倍增的思想,对于每个结点预处理其到第
查询时最坏情况下也只会跳转
预处理:
算法三
让我们把算法二先放在一边,考虑使用最长路径分解。
我们将树中的一条最长路径从树中删去,原树将被划分成若干子树,接下来再对子树递归地进行上述操作,最后原树会被分解为若干条不相交路径。
这个分解过程可以被描述为一棵树,我们姑且称其为分解树。分解树中的每一个结点都对应原树的一条路径,我们只需要存储分解树中每个结点的父亲结点,以及每个结点对应路径的一端到另一端的所有原树结点即可。
于是对于 LA 问题的一次查询,当
显然在最坏情况下,我们的分解树会变成一条链,且结点对应路径长度分别为
预处理出每个结点的深度和高度,上述信息可以在
预处理:
算法四
考虑对算法三进行优化,使用梯子分解。
在最长路径分解的基础上,我们对每条路径额外存储其根节点在原树上的若干祖先,额外存储的祖先数等于该路径的长度。
对于 LA 问题的一次查询,我们发现
预处理:
算法五
现在让我们把算法二和算法四结合起来。
对于 LA 问题的一次查询,我们先使用跳转指针跳最大的一步,此时答案的距离必定不超过刚刚跳的长度,即不超过当前位置的高度,于是我们可以通过额外存储的祖先立刻得到答案。
这就是 LA 问题的经典解法。
预处理:
算法六
考虑对算法五进一步优化。
查询的时间复杂度已经足够优秀了,我们考虑降低预处理的复杂度。预处理的瓶颈在于对于所有结点都存储了
我们发现要求一个结点的第
对于整棵树,我们只预处理叶子结点的
接下来我们考虑如何将任意树变为这样的一棵树。
剪枝(物理)
我们考虑将子树大小大于等于
此时宏观树叶子个数变为
接下来我们考虑对所有微观树使用算法一,由于大小为
此时我们已经在
至此,我们得到了一个花费