一.树链剖分的概念
树链剖分是一种对树进行划分的算法,将树划分为若干个链,以便维护树上路径的信息。
也就是说,我们将树划分为若干个线性结构,使用一些数据结构来维护它,如:树状数组,线段树,splay等。
二.树链剖分的作用
前面我们提到,我们将树划分为多个线性结构,用数据结构来维护它,所以我们可以很快捷地对于树上的某些信息进行修改或者查询等操作,例如:修改某条树上路径的所有点权;查询某条树上路径点权的极值、和、等。
三.重链剖分
我们先要了解一些很重要的定义。
重子节点:其子节点中子树大小最大的节点,若有多个,任取其一。若没有子节点,那么久没有重子节点。
轻子节点:其子节点中除重子节点以外的所有子节点。
重边:从当前节点到重子节点的边。
轻边:从当前节点到轻子节点的边。
重链:若干条相连接的重边。
这里给出一张摘自oi-wiki的图片供读者理解。
四.应用
我们先给出一些变量名的定义:
top[i]//表示节点i所在的重链的顶部节点。
dfn[i]//表示节点i的dfs序,也是节点i在线段树中的序号。
dep[i]//表示节点i的深度。
fa[i]//表示节点i的父亲。
siz[i]//表示节点i的子树的节点个数。
son[i]//表示节点i的重子节点。
sgt[i]//表示dfs序所对应的节点编号。
求两个节点 \(x\) 和 \(y\) 的路径上的点权之和:
int query_tree (int x, int y) {
int ans = 0;
while (top[x] != top[y]) {
//x和y不在同一条重链上。
if (dep[top[x]] < dep[top[y]]) {
swap (x, y);
}
ans += query (1, dfn[top[x]], dfn[x]);
//用线段树维护链上信息。
x = fa[top[x]];
//将x设为原重链链头的父节点,继续从轻边循环。
}
if (dep[x] > dep[y]) {
swap (x, y);
}
//x和y已经在同一条重链上了。
ans += query (1, dfn[x], dfn[y]);
//用线段树维护链上信息。
return ans;
}
这个操作就很像 \(LCA\) ,我们使用了 \(top\) 进行加速。
注意,我们每次循环的时候只能让同一个节点向上跳,否则会出现少计算答案的情况。
同理,修改树上某条路径的点权也是一样的。
求以节点 \(x\) 为根节点的子树内所有点权和:
我们知道,我们将树划分为了多个线性结构,也就是链。
于是,我们就可以利用一些数据结构进行维护。
所以,我们在查询子树和的时候,只需要查询一段区间的和。
这里我用线段树进行查询,代码如下:
query (1, dfn[x], dfn[x] + siz[x] - 1);
五.例题
这道题就是树链剖分的模板题。
对于 \(3\) , \(4\) 操作,我们可以将其化为线性结构进行维护。
而 \(1\) ,\(2\) 操作,我们只需要进行树上操作即可。
操作 \(1\) :
void addtree (int x, int y, int k) {
while (top[x] != top[y]) {
//不在同一条重链上。
if (dep[top[x]] < dep[top[y]]) {
swap (x, y);
}
addS (1, dfn[top[x]], dfn[x], k);
//线段树区间修改。
x = fa[top[x]];
//向上跳转。
}
if (dep[x] > dep[y]) {
swap (x, y);
}
//现在节点x和y在同一条重链上。
addS (1, dfn[x], dfn[y], k);
//线段树区间修改。
}
操作 \(2\) :
int query_tree (int x, int y) {
int ans = 0;
while (top[x] != top[y]) {
//不在同一条重链上。
if (dep[top[x]] < dep[top[y]]) {
swap (x, y);
}
ans += query (1, dfn[top[x]], dfn[x]);
//线段树区间查询。
ans %= p;
x = fa[top[x]];
//向上跳转。
}
if (dep[x] > dep[y]) {
swap (x, y);
}
//节点x和y在同一条重链上的时候。
ans += query (1, dfn[x], dfn[y]);
//线段树区间查询。
return ans %p;
}
操作 \(3\):
addS (1, dfn[x], dfn[x] + siz[x] - 1, y % p);
//运用线段树的区间修改。
操作 \(4\):
printf ("%d\n", query (1, dfn[x], dfn[x] + siz[x] - 1) %p);
//运用线段树的区间查询。