树链剖分
引入
对于树上的路径问题,我们无法像序列一样通过连续的下标预处理或用数据结构维护,我们需要一种算法将树上路径划分成若干个连续连续的区间,我们通过数据结构维护每区间将树上路径问题转换成对于若干个区间的维护问题,树链剖分因此而生。
树的dfn序
我们尝试像序列一样给一颗树上的节点编号,我们可以用多种方式给树节点标号,比较常用的一种是通过树的深度优先遍历序给树标号,我们称之为dfn序。
void dfs(int x) {
dfn[x] = ++df;
for (int y : e[x]) dfs(y);
}
引理1:一颗树的dfn序不唯一,由遍历顺序决定
引理2: 只有当前节点遍历的第一个儿子节点会和当前节点处于同一条dfn序连续的链上
引理3:任意一颗子树的dfn序集合为一段连续的区间
令 \(dfn_x\) 表示 \(x\) 的dfn序,\(size_x\) 表示以 \(x\) 为根的子树的节点数量即子树大小。
证明:设当前遍历节点为 \(x\),遍历的第一个儿子为 \(y\),第二个儿子为 \(z\)。显然有 \(dfn_y=dfn_x+1\),而由于 \(z\) 需要等待 \(y\) 子树全部被遍历后才能被访问,所以\(dfn_z=dfn_x+size_y+1\)。引理1,2得证。同理,\(x\) 子树外的节点也需要等待 \(x\) 的子树全部被访问完后才能被访问,故 \(x\) 与其子树构成的dfn区间为 \([dfn_x,dfn_x+sz_x-1]\),其中减一是减去了 \(x\) 本身,引理3得证。
如何通过dfn序来维护树上路径
我们的核心思想是通过将树划分成若干连续区间,通过数据结构维护这些区间,在查询或修改时。假设路径上有 \(k\) 个连续段,不算上数据结构的成本我们只需要 \(O(k)\) 的时间代价就可以踩着这些区间迅速达到目标节点而不是每一个点都要访问一遍。而由于引理1树的dfn序不唯一,我们的目的就变为了找到一种遍历方式,使得构建出来的所有dfn链的跳跃成本 \(O(k)\) 尽量小,这就是树链剖分实现的算法。
首先我们观察一种朴素的遍历方式


浙公网安备 33010602011771号