「学习笔记」树链剖分
轻重链剖分
树链?剖分?
树链剖分,顾名思义是把一棵树剖分成若干条链。
轻重链剖分满足每个结点属于且只属于一条重链。
首先引出概念:
| 名称 | 概念 |
|---|---|
| 重儿子 | 父结点的子树结点数量最多的子结点 |
| 轻儿子 | 父结点中除了重儿子以外的儿子 |
| 重边 | 连接父结点和重儿子的边 |
| 轻边 | 连接父结点和轻儿子的边 |
| 重链 | 若干条重边连接而成的路径 |
| 轻链 | 若干条轻边连接而成的路径 |

现在有一棵树,对于该树,从根结点开始寻找重儿子,并且其重儿子也向下寻找重儿子,最终这些重边连成一条重链。而根结点其他的儿子(即轻儿子)也各自寻找自己的重儿子,使得整棵树分成若干条重链。


实现
剖分的过程由两个dfs实现。
| 名称 | 解释 |
|---|---|
| fa[u] | 结点u的父结点 |
| dep[u] | 结点u的深度 |
| size[u] | 以结点u为根的子树的结点数目 |
| son[u] | 结点u的重儿子 |
| rk[u] | 结点u的dfs标号在树中所对应的结点 |
| top[u] | 结点u所在链的顶端结点 |
| id[u] | 结点u的dfs序 |
标记每个结点的 father,dep,size,并找出每个结点的重儿子。
void dfs1(int u , int father , int depth) {
fa[u] = father;//记录父结点
dep[u] = depth;//记录深度
size[u] = 1;//将当前子树size标记为1
for (int i = head[u]; i; i = edge[i].next) {
if (edge[i].to != father) {
dfs1(edge[i].to , u , depth+1);//深度++
size[u] += size[edge[i].to];//更新父结点的 size
if (size[edge[i].to] > size[son[u]]) {
son[u]=edge[i].to;//选取size最大的子结点作为重儿子
}
}
}
}
调用:
dfs1(root , 0 , 1)
连接重链,标记dfs序,处理出数组top,id,rk。
void dfs2(int u , int t) {
top[u] = t;//通过标记当前链的链首来连接重链
id[u] = ++cnt;//记录dfs序
rk[cnt] = u;//记录dfs序指向的节点
if (!son[u]) return;
dfs2(son[u] , u);//优先延伸重链,使同一条重链上的dfs序连续
for (int i = head[u]; i; i = edge[i].next) {
if (edge[i].to != son[u] && fa[u]) {
dfs2(edge[i].to , edge[i].to);
}
}
}
时间复杂度
树链剖分的两个性质:
- 如果(u, v)是一条轻边,那么 ;
- 从根结点到任意结点的路所经过的轻重链的个数必定都小于 .
可得,树链剖分的时间复杂度为 或者写做…… 。
显然是一种优秀的算法。

浙公网安备 33010602011771号