树链剖分
树链剖分的概念
剖分,就是把路径分类为重链和轻链 。树链剖分就是把一些点合成一条路径,使其在新序列中的编号(下标)有序 ,这样可以方便的计算,使得查询、修改的效率大大提高。
什么是重链?
假设一棵树a,根节点为NOW,该结点有若干个儿子,其中有一个子树的节点数最大,这个子树的根(也就是NOW的一个儿子)被称作重节点,从NOW到重节点的路径成为重链。可以证明,每棵树的重链不超过个条,所以时间效率就为了,比树上直接dfs快得多。
什么是轻链?
与重链相反,不是重节点的就是轻节点,不是重链的就是轻链,轻链同样最多有条。
树链剖分定义
1.一棵树上最都有 条重链,
2.如果(u, v)是一条轻边,那么size(v) < size(u)/2;
3.从根结点到任意结点的路所经过的轻重链的个数必定都小与;
证明:
由于任一轻儿子对应的子树大小要小于父节点所对应子树大小的一半(定义2)
因此从一个轻儿子沿轻边向上走到父节点后 所对应的子树大小至少变为两倍以上
经过的轻边条数自然是不超过的
然后由于重链都是间断的 (连续的可以合成一条)
所以经过的重链的条数是不超过轻边条数+1的
因此经过重链的条数也是级别的
数组
名称 解释siz[u] 保存以u为根的子树节点个数top[u] 保存当前节点所在链的顶端节点son[u] 保存重儿子dep[u] 保存结点u的深度值faz[u] 保存结点u的父亲节点tid[u] 保存树中每个节点剖分以后的新编号(DFS的执行顺序)rnk[u] 保存当前节点在树中的位置
算法实现
需要做两边dfs,第一遍dfs求出每个节点的深度(deep[]),每个节点的子树中节点个数(size[]),每个节点的父亲(fa[]),每个节点的重儿子(son[])
void dfs1(LL now,LL pre,LL dep){
//pre是NOW的父亲,dep是深度
size[now]=1;
deep[now]=dep;
fa[now]=pre;
for(LL i=head[now];i!=-1;i=nxt[i]){
LL to=pnt[i];
if(to==pre) continue;
dfs1(to,now,dep+1);
size[now]+=deep[now];
if(son[now]==-1 || size[to]>size[son[now]]) son[now]=to;
}
}
void dfs1(LL now,LL pre,LL dep){
//pre是NOW的父亲,dep是深度
size[now]=1;
deep[now]=dep;
fa[now]=pre;
for(LL i=head[now];i!=-1;i=nxt[i]){
LL to=pnt[i];
if(to==pre) continue;
dfs1(to,now,dep+1);
size[now]+=deep[now];
if(son[now]==-1 || size[to]>size[son[now]]) son[now]=to;
}
}
第二遍dfs求出每个节点的dfs序号(tid[]),每个dfs序号对应的树上的点(rnk[]),每个重节点所在重链的顶端(top[]),轻节点的顶端都为自己。
void dfs2(LL now,LL t){
++cnt;//cnt是剖分后序列编号
top[now]=t;
tid[now]=cnt;
rnk[cnt]=now;
if(son[now]==-1) return ;
dfs2(son[now],t);
for(LL i=head[now];i!=-1;i=nxt[i]){
LL to=pnt[i];
if(to!=son[now] && to!=fa[now]) dfs2(to,to);
}
}
void dfs2(LL now,LL t){
++cnt;//cnt是剖分后序列编号
top[now]=t;
tid[now]=cnt;
rnk[cnt]=now;
if(son[now]==-1) return ;
dfs2(son[now],t);
for(LL i=head[now];i!=-1;i=nxt[i]){
LL to=pnt[i];
if(to!=son[now] && to!=fa[now]) dfs2(to,to);
}
}
接下来其他操作就可以在新序列中进行操作,比如可以线段树维护区间最小值和区间和。
求lca一样求,重节点跳到top,每次一个个跳,直到两个点在一条重链上。
模板题:bzoj1036