树链剖分-重链剖分
重链剖分
是指一种对树进行划分的算法, 它先通过轻重边剖分(Heavy-Light Decomposition)将树分为多条链, 保证每个点属于且只属于其中一条链, 然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链.
定义
size[i] 以结点i为根的子树中结点的个数.son[i] 结点i的重儿子.dep[i] 结点i的深度, 根的深度为1.top[i] 结点i所在重链的链首结点.fa[i] 结点i的父结.dfn[i] 在DFS找重链的过程中为结点i重新编的号码, 每条重链上的结点编号是连续的.rnk[i] 链上节点对应的树上节点编号, 用于访问初值.
那么, 令 $V$ 是 $U$ 的儿子节点中size值最大的节点, 称 $V$ 是 $U$ 的重儿子重边, 除此之外, 所有的边称为轻边. 全部由重边构成的路径称为重链.
预处理
第一次DFS(预处理)
从根节点出发, 递归遍历整棵树, 计算每个节点的size、dep、fa, 并找出每个节点的重儿子son(即子树最大的儿子).第二次DFS(分配编号)
再次从根节点出发, 优先遍历重儿子, 将每个节点分配一个新的编号dfn, 并确定每个节点所在重链的链首top.
代码示例
1 | void dfs1(int u, int f) { |
常见操作
单点处理
要处理 x 节点上的值, 直接访问 dfn[x] 即可.
路径上维护
由于链上的 dfn 都是连续的, 可以用线段树/树状数组直接维护这些重链.
但是大多数时间任意两个节点形成的路径并不是一条链. 我们的目标是让这两个节点到同一个重链上以便我们维护. 让 dep[top] 更大的节点往上跳到top, 同时修改这一段重链, 直到两个点到同一个重链上. 到同一重链后也要操作一下.
注意, 上述操作应跳至fa[top[x]],修改dfn[top[x]] ~ dfn[x], 以便跳到靠上一条的链.
对于边权转点权, 应当注意在最后一次操作时不能对lca操作, 其对应的边并不在操作的路径上.
参考代码
正常点权:
1 | int ans = 0; |
边权转点权:
1 | int sum = 0; |
子树上维护
由dfs2的编号过程可知, 一个子树的dfn值使连续的. 由此可知, 我们可以直接对一个子树进行更改. 更改范围: $dfn_x \sim dfn_x+size[x]-1$
例题
例题 P2590 [ZJOI2008] 树的统计
按照题意维护即可
完整代码
1 |
|
例题 P3384 【模板】重链剖分 / 树链剖分
这是一道正常的点权题, 有区间修改.
完整代码
1 |
|
例题 P3950 部落冲突 - 洛谷
这是一道边权转点权的树链剖分题, 只有单点修改.
- 对于一次战争, 直接把对应的路径设为
1, 也就是更改较深的节点. - 对于查询, 查询路径上是否有
1, 这里用和或最大值均可. - 对于停战, 把路径改为
0即可.
需要注意查询 u=v 时直接输出 Yes.
完整代码
1 |
|
例题 P2146 [NOI2015] 软件包管理器 - 洛谷
好久没有写过树链剖分的题了, 写一道练练手.
- 对于安装操作, 答案就是从
根到其的长度-根到其的路径上安装的数量. 然后将这条路径上所有节点改为安装状态. - 对于卸载操作, 答案就是其子树中已安装的软件数量, 然后将子树全部变为未安装状态.
由以上可以总结出一种维护方法: 对于已安装的节点标记为 1, 未安装的是 0. 维护路径赋值和路径总和即可.
完整代码
1 |
|