Zirnc's Blog

树链剖分(本文仅介绍 重链剖分(Heavy-Light Decomposition))的用途:

思想

树链剖分即把整棵树剖分成若干条链,然后用线段树等数据结构来维护链上的信息。重链剖分可以将树上的任何一条路径划分成不超过 $O(\log n)$ 条连续的链。

定义:

把落单的结点也当作重链,那么整棵树就被剖分成若干条重链。

性质

实现

模板 RECORD

定义:

需要用两次 DFS 求出以上的所有值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void dfs1(int u, int f) {
  fa[u] = f;
  dep[u] = dep[f] + 1;
  siz[u] = 1;
  for (int i = 0; i < g[u].size(); i++) {
    int v = g[u][i];
    if (v == f) continue;
    dfs1(v, u);
    if (son[u] == 0 || siz[v] > siz[son[u]]) son[u] = v;
    siz[u] += siz[v];
  }
}

void dfs2(int u, int topp) {
  top[u] = topp;
  dfn[u] = ++cnt;
  rnk[dfn[u]] = u;
  if (son[u] != 0) dfs2(son[u], topp); // 小心!要判断该节点有无重儿子
  for (int i = 0; i < g[u].size(); i++) {
    int v = g[u][i];
    if (v == fa[u] || v == son[u]) continue;
    dfs2(v, v);
  }
}

然后用 DFN 序来代表每一个点,套上普通的线段树:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void build(int u, int l, int r) {
  if (l == r) {
    tree[u].maxx = tree[u].sum = w[rnk[l]];
    return;
  }
  build(u << 1, l, mid);
  build(u << 1 | 1, mid + 1, r);
  pushup(u);
}
// ... 修改查询什么的的就不贴上来了

处理路径信息

处理两个点 $u$,$v$ 之间的信息,重复以下步骤:

  1. 如果 $u$,$v$ 不在同一条链上,设 $u$ 所在链链顶深度较大,则查出 $u$ 的链顶到 $u$ 的信息(依据同一条重链上 DFN 序是连续的),然后将 $u$ 跳到 $u$ 所在链链顶的父亲
  2. 如果 $u$,$v$ 在同一条链上,直接查询。结束。
1
2
3
4
5
6
7
8
9
int ans = -INF;
while (top[x] != top[y]) {
  if (dep[top[x]] < dep[top[y]]) swap(x, y);
  ans = max(ans, queryMax(1, 1, cnt, dfn[top[x]], dfn[x]));
  x = fa[top[x]];
}
if (dep[x] > dep[y]) swap(x, y);
ans = max(ans, queryMax(1, 1, cnt, dfn[x], dfn[y]));
cout << ans << endl;

运用同样的方法,可以求出两个点的 LCA。

处理子树信息

根据一颗子树内的 DFN 序是连续的,我们可以记录所在子树连续区间末端的结点,就可以把子树转化为连续的一段区间。

参考资料

🙇‍

#OI #算法 #数据结构 #树链剖分