轻重链剖分
树链?剖分?
树链剖分,顾名思义是把一棵树剖分成若干条链。
轻重链剖分满足每个结点属于且只属于一条重链。
首先引出概念:
名称 | 概念 |
---|---|
重儿子 | 父结点的子树结点数量最多的子结点 |
轻儿子 | 父结点中除了重儿子以外的儿子 |
重边 | 连接父结点和重儿子的边 |
轻边 | 连接父结点和轻儿子的边 |
重链 | 若干条重边连接而成的路径 |
轻链 | 若干条轻边连接而成的路径 |
现在有一棵树,对于该树,从根结点开始寻找重儿子,并且其重儿子也向下寻找重儿子,最终这些重边连成一条重链。而根结点其他的儿子(即轻儿子)也各自寻找自己的重儿子,使得整棵树分成若干条重链。
实现
剖分的过程由两个dfs实现。
名称 | 解释 |
---|---|
fa[u] | 结点u的父结点 |
dep[u] | 结点u的深度 |
size[u] | 以结点u为根的子树的结点数目 |
son[u] | 结点u的重儿子 |
rk[u] | 结点u的dfs标号在树中所对应的结点 |
top[u] | 结点u所在链的顶端结点 |
id[u] | 结点u的dfs序 |
1.
\large\mathcal{1.}
1.
标记每个结点的 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)
2.
\large\mathcal{2.}
2.
连接重链,标记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)是一条轻边,那么 s i z e ( v ) < s i z e ( u ) ÷ 2 \mathcal{size(v)<size(u)÷2} size(v)<size(u)÷2;
- 从根结点到任意结点的路所经过的轻重链的个数必定都小于 log n \log n logn.
可得,树链剖分的时间复杂度为
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n) 或者写做……
O
(
n
(
log
n
)
2
)
O(n(\log n)^2)
O(n(logn)2)。
显然是一种优秀的算法。