将树分割成多条链,然后用线段树等来维护这些链。
树链剖分的分割标准:连接一个节点的子树中的 “ 重儿子 ” // 也就是结点最多的儿子,依次连下去。
实现
定义:
重儿子
:子树节点的儿子
轻儿子
:除了重儿子以外的儿子
重边
:父节点与重儿子组成的边
轻边
:除重边以外的边
重链
:重边连接而成的链
轻链
:轻边连接而成的链
链头
:一条链上深度最小的点
对于树链剖分,我们需要维护以下的数组:
名称 | 含义 |
---|---|
siz[u] | 保存以u为根的子树节点个数 |
son[u] | 保存u的重儿子 |
top[u] | 保存u所在链的顶端节点 |
dep[u] | 保存结点u的深度值 |
faz[u] | 保存结点u的父亲节点 |
dfn[u] | u的DFS序 |
rk[u] | u树中的编号 |
DFS_1:预处理 siz, son, dep, faz数组
void Dfs1(int u, int father, int depth) { dep[u] = depth; faz[u] = father; siz[u] = 1; for(int i = head[u]; i; i = edge[i].next) { int v = edge[i].to; // 如果连接的结点是当前结点的父亲结点,则不处理 if(v != faz[u]) { Dfs1(v,u,depth+1); siz[u] += siz[v]; if(son[u] == -1 || siz[v] > siz[son[u]]) son[u] = v; } } return; }
DFS_2:预处理dfn, top, rk 数组。
void Dfs2(int u, int t) // t:起始的重节点 { top[u] = t; dfn[u] = cnt; rk[cnt] = u; cnt++; if(son[u] == -1) return; Dfs2(son[u], t); for(int i = head[u]; i; i = edge[i].next) { int v = edge[i].to; if(v != son[u] && v != faz[u]) Dfs2(v,v); } return; }
维护
修改:
void modifyOnTree(int u, int v, int val) { // 更新节点 u 到节点 v 的值 while(top[u] != top[v]) { // 不妨设dep[top[u]] > dep[top[v]] if(dep[top[u]] < dep[top[v]]) swap(u,v); Modify(1, 1, n, dfn[top[u]], dfn[u], val); u=faz[top[u]]; } if(dep[u] > dep[v]) swap(u,v); Modify(1, 1, n, dfn[u], dfn[v], val); }
查询:
int queryOnTree(int u, int v) { // 求和 int res = 0; while(top[u] != top[v]) { if(dep[top[u]] < dep[top[v]]) swap(u,v); res += Query(1, 1, n, dfn[top[u]], dfn[u]); u = faz[top[u]]; } if(dep[u] > dep[v]) swap(u,v); res += Query(1, 1, n, dfn[u], dfn[v]); return res; }