Intro to HLD
buaa prime21
树上的路径询问
区间上的RMQ! RMQ上树?
区间上的求和! 求和上树?
区间上的gcd! gcd上树?
区间翻转! 翻转上树?
猴子? 猴子上树?
heavy-light-decomposition
size:子树大小
重儿子:节点所有儿子中size最大的一个
重边:(heavy edge)每个节点与其重儿子的边
轻边:(light edge)节点与重儿子以外的儿子的边
重链:(heavy path)由重边组成的一条链
算法流程-预处理
dfs1:求出如下信息fa,size,son,deep
其中son表示一个节点的重儿子
dfs2:top--该点所属重链中deep最小的点
idx--该点属于第idx条重链
idy--该点为重链重深度第idy小的
int num_invl;
int idx[N];
int idy[N];
int top[N];
void dfs2(node p){
if (have_son(p)){
top[son[p]]=top[p];
idy[son[p]]=idy[p]+1;
idx[son[p]]=idx[p];
dfs2(son[p]);
}
for (v:p的所有儿子)
if (v!=son[p])
{
top[v]=v;
num_invl++;
idx[v]=num_invl;
idy[v]=1;
dfs2(v)
}
}
算法流程-链操作
void opt(node u,v){
node fu=top[u],fv=top[v];
while (fu!=fv){
if (deep[fu]<deep[fv]){
swap(fu,fv); swap(f,v);
}
operation(idx[fu],idy[fu],idy[u]);
u=fa[fu];
fu=top[u];
}
if (deep[u]<deep[v]) swap(u,v);
operation(idx[u],idy[v],idy[u]);
}
opt(u,v)实现了从树上u,v两点间所有节点进行某种操作
算法流程-预处理算法的改进
观察dfs2可知,一条重链在dfs2的dfs序中常常是连续的一段,为了简化代码复杂度,我们不需要记录idx,idy只需要记该点在此dfs序中的位置即可
int df_num;
int pos[N];
int top[N];
void dfs2(node u){
pos[u]=++df_num;
if (have_son(u)){
top[son[u]]=top[u];
dfs2(son[u]);
}
for (v: u的所有儿子)
if (v!=son[u]){
top[v]=v;
dfs2(v);
}
}
算法流程-链操作的改进
同理可以改写链操作
void opt(node u,v){
node fu=top[u],fv=top[v];
while (fu!=fv){
if (deep[fu]<deep[fv]){
swap(fu,fv); swap(f,v);
}
operation(pos[fu],pos[u]);
u=fa[fu];
fu=top[u];
}
if (deep[u]<deep[v]) swap(u,v);
operation(pos[v],pos[u]);
}
改进前和改进后并无本质的区别,改进后代码复杂度更小一些,改进前的写法便于理解,便于思考从本质上处理问题
时间复杂度证明
预处理阶段:每个点在两个dfs中恰好被访问一次O(N)
剖分后的树有一下两个性质:
1.(v,u)为一条轻边,size[v]>size[u]*2
2.从根到某一点的路径经过轻边和重链的个数都不大于O(logN)
询问阶段:两个点到LCA的路径上经过的轻边和重链的个数都不大于O(logN),设在重链上询问操作复杂度为O(Q)
故总复杂度:O(N)-O(QlogN)
树链剖分的意义
以一个log的代价在树上推广若干数据结构和算法
Easy
给定一棵树,依次给出N-1条带权边
支持以下两种操作:
1.CHANGE i ti : 将第i条边的边权改为ti
2.QUERY a b :求a到b的路径中边权最大的边
hint:边操作?父边?
Medium-Easy
给定一颗树,一开始树上所有节点都为白色,支持以下两种操作:
1. 0 i 改变第i个节点的颜色(由黑变白,由白变黑)
2. 1 v 询问从根到v上第一个黑色节点的位置,如果不存在输出-1
sgt or heap
Intro to HLD
By prime21
Intro to HLD
- 934