重链剖分的介绍
在没有特殊说明的情况下,树链剖分都是指重链剖分,⽤于将树分割成若⼲条链的形式,以维护树上路径的信息。可以⽤ ⼀些数据结构来维护。
- 重链剖分可以将树上的任意⼀条路径划分为不超过 O ( log N ) O(\log{N}) O(logN) 条连续的链,每条链上的深度互不相同,链上所有 点的 L C A LCA LCA 为链的⼀个端点,可以保证划分出的每条链上的节点 D F S DFS DFS 序连续。下⾯是⼀些相关的定义:
- 重⼦节点:表示其⼦节点中⼦树最⼤的⼦结点。如果有多个⼦树最⼤的⼦结点,取其⼀。如果没有- - ⼦节点,就⽆重⼦ 节点。
- 轻⼦节点:表示剩余的所有⼦结点,即除了重⼦节点以外所有的节点。
- 重边:⽗节点到重⼦节点的边。
- 轻边:⽗节点到其他轻⼦节点的边。
- 重链:若⼲条⾸尾衔接的重边,其中落单的结点也当作重链。
- 轻链:由多条轻边连接⽽成的路径。
已知以下性质: - 在轻边 ( u , v ) (u,v) (u,v) 中, s i z e ( u ) ≤ s i z e ( u / 2 ) size(u) \leq size(u/2) size(u)≤size(u/2)。
- 从根到某⼀点的路径上,不超过 log N \log{N} logN 条轻链和不超过 log N \log{N} logN 条重链。
- 叶节点没有重⼦节点,⾮叶⼦节点有且只有⼀个重⼦节点,轻⼦节点之后不⼀定全是轻⼉⼦。
- 树上每个节点都属于且仅属于⼀条重链,并且所有的重链将整棵树完全剖分。
重链剖分的实现
进⾏两次
D
F
S
DFS
DFS 来实现重链剖分。
第⼀次
D
F
S
DFS
DFS 记录每个节点的⽗节点,深度,⼦树⼤⼩,重⼦节点。刚开始搜到节点
u
u
u 时以
u
u
u 为根节点的⼦ 树⾥⾯只有
u
u
u ⼀个节点。当搜到与
u
u
u 连向的节点
v
v
v 时,需要判断⼀下
v
v
v 有没有被搜到过,如果没有,那么 记录⼀下⽗亲和
v
v
v 的深度,然后继续往下搜,⼀直搜到叶⼦节点为⽌。搜索完之后回溯,更新以
u
u
u 为根的⼦树的⼤ ⼩。根据重⼦节点的定义,如果以
u
u
u 的⼦树中,以
v
v
v 为根的⼦树节点多,那就更新⼀下
u
u
u 的重⼉⼦为
v
v
v。 代码如下:
def dfs1(u, depth):
global sz
global son
dep[u] = depth
sz[u] = 1
for x in attr[u]:
if x == fa[u]:
continue
fa[x] = u
dfs1(x, depth + 1)
sz[u] += sz[x]
if not son[u] or sz[x] > sz[son[u]]:
son[u] = x
第⼆次 D F S DFS DFS 记录节点 u u u 所在链的链顶 t t t,重边优先遍历时的 D F S DFS DFS 序,以及 D F S DFS DFS 序下当前点的点值和 题⽬中给出的节点 u u u 的点值。在求出来重⼦节点后,轻⼦节点需要被连成链,则需要让链上的点连续。从根开始 D F S DFS DFS,如果有重⼦节点就先⾛重⼦节点,搜到叶⼦节点后回溯回去再去搜轻⼦节点。代码如下:
def dfs2(u, tp):
global ts
global top
global dfn
global nw
top[u] = tp
dfn[u] = ts
nw[ts] = w[u]
ts += 1
if not son[u]:
return
dfs2(son[u], tp)
for x in attr[u]:
if x == fa[u] or x == son[u]:
continue
dfs2(x, x)
重链剖分的基本操作
重链剖分⽀持修改和查询两种操作。因为重链剖分需要维护的是链,并且链上的 D F S DFS DFS 序连续,所以可以直接取节点 u u u 所在链的链顶 t t t 到它⾃⼰这⼀段进⾏修改或者查询,结束之后将当前节点跳到 t t t 的⽗节点,因为 t t t 已经 被查询或修改过了,跳到上⼀个节点可以防⽌重复操作。⼀般情况下,我们选取链顶较深的结点进⾏查询或修改,然后在往 上跳,这样可以避免死循环。下⾯给出代码:
u, v, k = op[1:]
while top[u] != top[v]:
if dep[top[u]] < dep[top[v]]:
u, v = v, u
modify(1, dfn[top[u]], dfn[u], k)
u = fa[top[u]]
if dep[u] < dep[v]:
u, v = v, u
modify(1, dfn[v], dfn[u], k)