This blog post is only available in Simplified Chinse.
树链剖分,可以把一棵树划分成多条链,对于每条链可以用线段树等数据结构进行维护,将树形结构的问题转化。
常见的方法是轻重链剖分,即 Heavy-Light Decomposition。
[notice]这是一篇意识流式的总结,如果要入门树链剖分请不要看这篇……[/notice]
前置知识
LCA、线段树……
一些定义
需要构造如下数组:
size[x]
:以 x 为根的子树的节点总数;deep[x]
:x 的深度;son[x]
:x 通过重链通向的儿子节点;fa[x]
:x 的父节点;top[x]
:x 所在重链的顶端节点;id[x]
:节点 x 在线段树中的下标位置;v[x]
:节点 x 的权值(对于边权,可以把边的权值归属于其下面的点,转化为点权处理)。
另建一棵线段树存储重链信息。
如果 v 是 u 的儿子中 size 最大的一个,则 (u,v) 是重边,v 是 u 的重儿子,u 通向的其他儿子是轻边。
连续的重边形成重链。通过 id[]
可以将一条重链映射到线段树上的一条线段。
关于复杂度的结论
结论一: 如果 为轻边,则 size[v]
不会大于 size[u]/2
。
结论二: 重链和轻边的数量级相当。
由此可以得到:
结论三: 从某一个点到根上经过的重链或轻边的总数在 级别。这是树链剖分的核心。
据此,对于一条链上的操作可以做到 。对于链上的修改,整条重链可以对应线段树的一个区间,存储 top[]
可以实现直接调到重链顶端,达到优化时间复杂度的目的。
实现
第一遍 DFS 构造出 size[],son[],fa[],deep[]
,第二遍 DFS 造出 top[],id[]
。
对于两点的路径修改,直接求出 LCA 后一直往上跳即可。
对于子树修改,直接当成 DFS 序处理。
对于构造 id[]
的 DFS 需要注意:本质上 id[]
是一个时间戳,即 DFS 序。为了使得重边在造出来的序中连续,便于线段树的处理,在遍历的时候要先遍历重儿子。涉及子树的操作也会方便很多。
代码
洛谷模板题 P3384:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
// #define int long long
inline int read(){
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
const int maxn=1e5+5;
const int maxe=2*maxn;
int n,m,root,tt;
void add(int &x,int y){
x=(x+y%tt)%tt;
}
{
:
tree[maxn*],tag[maxn*];
{
(tree[ls],tag[p]*(mid-tl)%tt);
(tag[ls],tag[p]);
(tree[rs],tag[p]*(tr-(mid))%tt);
(tag[rs],tag[p]);
tag[p]=;
}
:
(){
(tree,,(tree));
(tag,,(tag));
}
{
(tl==tr && tl==x){
(tree[p],delta);
;
}
(tl,tr,p);
(x<=mid ) (x,tl,mid,ls,delta);
(mid<=x) (x,mid,tr,rs,delta);
tree[p]=(tree[ls]+tree[rs])%tt;
}
{
(sl<=tl && tr<=sr){
(tree[p],(tr-tl)*delta%tt);
(tag[p],delta);
;
}
(tl,tr,p);
(sl<=mid ) (sl,sr,tl,mid,ls,delta);
(mid<=sr) (sl,sr,mid,tr,rs,delta);
tree[p]=(tree[ls]+tree[rs])%tt;
}
{
(tl==tr && x==tl) tree[p]%tt;
(tl,tr,p);
ret=;
(x<=mid ) (ret,(x,tl,mid,ls));
(mid<=x) (ret,(x,mid,tr,rs));
ret;
}
{
(sl<=tl && tr<=sr) tree[p]%tt;
(tl,tr,p);
ret=;
(sl<=mid ) (ret,(sl,sr,tl,mid,ls));
(mid<=sr) (ret,(sl,sr,mid,tr,rs));
ret;
}
};
tot=,lnk[maxn],nxt[maxe],to[maxe];
size[maxn],deep[maxn],fa[maxn];
v[maxn];
{
tot++;to[tot]=y;
nxt[tot]=lnk[x];lnk[x]=tot;
}
f[maxn][];
{
(deep[x]<deep[y]) (x,y);
( i=;i>=;i--) (deep[f[x][i]] >= deep[y]) x=f[x][i];
(x==y) x;
( i=;i>=;i--) (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
fa[x];
}
HLD{
son[maxn],top[maxn],id[maxn];
{
size[x]=;
f[x][]=fa[x]; ( i=;i<=;i++) f[x][i]=f[f[x][i]][i];
max_size=,k=;
( i=lnk[x];i;i=nxt[i]) (to[i]!=fa[x]){
fa[to[i]]=x;deep[to[i]]=deep[x];
(to[i]);
size[x]+=size[to[i]];
(size[to[i]]>max_size) max_size=size[to[i]],k=to[i];
}
(k!=) son[x]=k;
}
{
id[x]=++id[];
(son[fa[x]]!=x) top[x]=x; top[x]=top[fa[x]];
(son[x]) (son[x]);
( i=lnk[x];i;i=nxt[i]) (to[i]!=fa[x] && to[i]!=son[x]){
(to[i]);
}
}
{
(x!=l){
(top[x]!=x){
(deep[top[x]]>=deep[l]) tree.(id[top[x]],id[x],,n,,delta),x=top[x];
tree.(id[l],id[x],,n,,delta),x=l;
} tree.(id[x],,n,,delta),x=fa[x];
}
tree.(id[x],,n,,delta);
}
{
l=(x,y);
(x,l,delta,tree);
(y,l,delta,tree);
tree.(id[l],,n,,-delta);
}
{
ret=;
(x!=l){
(top[x]!=x){
(deep[top[x]]>=deep[l]) (ret,tree.(id[top[x]],id[x],,n,)),x=top[x];
(ret,tree.(id[l],id[x],,n,)),x=l;
} (ret,tree.(id[x],,n,)),x=fa[x];
}
(ret,tree.(id[x],,n,));
ret;
}
{
l=(x,y);
((x,l,tree)+(y,l,tree)-tree.(id[l],,n,)+tt)%tt;
}
{
tree.(id[x],id[x]+size[x],,n,,delta);
}
{
tree.(id[x],id[x]+size[x],,n,);
}
}
SegmentTree tree;
{
n=(),m=(),root=(),tt=();
( i=;i<=n;i++) v[i]=();
( i=;i<n;i++){
x=(),y=();
(x,y);(y,x);
}
deep[root]=;
HLD::(root);
HLD::(root);
( i=;i<=n;i++) tree.(HLD::id[i],,n,,v[i]);
(m--){
opt=(),x,y,z;
(opt){
:
x=(),y=(),z=()%tt;
HLD::(x,y,z,tree);
;
:
x=(),y=();
(,HLD::(x,y,tree));
;
:
x=(),z=()%tt;
HLD::(x,z,tree);
;
:
x=();
(,HLD::(x,tree));
;
}
}
;
}