树链剖分总结
基本思想
对于有些题目,我们往往需要对一些树链进行修改或者查询,而我们不可能每次都暴力修改暴力枚举,这样做的时间复杂度很高,而很多有修改和查询操作的数据结构例如线段树和树状数组又不能在树上进行,这个时候,我们就引进了一种算法,树链剖分,将一颗树划分为若干条链,再用数据结构去维护。
树剖的一些基础概念
重儿子和轻儿子
重儿子指的是对于一个节点来说,他的儿子中\(size\)最大的那一个为他的重儿子(若有多个则任取一个),其它的兄弟儿子为轻儿子(下面的重节点与轻节点跟这里一样)
重边和轻边
一个节点与它的重儿子的连边叫重边,与轻儿子的连边叫轻边。
重链
连续的重边连接起来就是一条重链
链顶
一个节点所在重链的深度最小的节点为链顶,轻节点的链顶为自身(一般用于跳链)
[][1]
[1]: 上图中带红点的节点为轻节点,加粗的边为重边,而连续重边就是一条重链,例如1 4 9 13 14构成一条重链,3 7构成一条重链,2 6 11构成一条重链。性质
一条树链上重链的条数最多为log条
因为每跳一次轻边都会至少将\(size*2\) 而重链一跳就跳到顶实现过程
一般来说树剖需要两遍dfs进行预处理,第一遍求出每个节点的重儿子,父亲,第二遍构建重链并求出每个节点所在重链的顶端,如果不是重链则顶端为自己,并且将每个点重新编号,按dfs序编号,每次优先走重边,这样重边所连的子树的所有节点dfs序一定小于轻边所连的子树,且重链上节点的新编号一定是连续的。
void dfs1(int f,int u){ fa[u]=f; size[u]=1; for(int i=h[u],s=0;i;i=nex[i]) { int v=to[i]; if(v==f)continue; dfs1(u,v); size[u]+=size[v]; if(size[v]>s)s=size[v],son[u]=v; }}int dfs2(int u){ dfn[u]=++cnt; int x=dfn[u]; a[x]=aa[u]; if(!top[u])top[u]=u; if(son[u]){top[son[u]]=top[u];dfs2(son[u]);} for(int i=h[u];i;i=nex[i]) { int v=to[i]; if(v==son[u]||v==fa[u])continue; x=max(x,dfs2(v)); } return x;}
修改与询问
修改与询问其实差不多,原数据结构几乎可以照搬,按我们上述的方法进行重新编号之后会发现,每一条重链上的节点或者每一棵子树上点节点编号都是连续的。然后我们会发现,对两点间最短路的操作其实就是对两点间每条重链的一个区间进行操作,不断的跳链,再不断维护跳过即可,以修改操作为例
while(top[x]!=top[y])//跳链的前提是两点不在同一条重链上 { if(dfn[x] dfn[y])swap(x,y); update(1,1,n,dfn[x],dfn[y],z);/*此时两点已经跳到同一条重链上,但这条重链上的被包含的区间还没有维护,继续维护。*/
我们可以看到事实上树链剖分的作用就是将所需维护的区间找出来,所有的维护操作与原数据结构一样,不需要任何修改,查询操作与修改基本相同。
总结
树链剖分是一种让线性数据结构能够在树上进行维护的一种算法,时间复杂度为原数据结构的复杂度*log。
代码(以线段树为例)
题目来源(洛谷P3384【模板树链剖分)
#include#define ll long long#define lc (no<<1)#define rc (no<<1|1)#define ls lc,l,mid#define rs rc,mid+1,rusing namespace std;inline int gi(){ char a=getchar();int b=0; while(a<'0'||a>'9')a=getchar(); while(a>='0'&&a<='9')b=b*10+a-'0',a=getchar(); return b;}const int N=1e6+20;ll aa[N],a[N],h[N],to[N],nex[N],dfn[N],fa[N],son[N],top[N],size[N],t[N],lazy[N],n,m,root,p,cnt;void dfs1(int f,int u){ fa[u]=f; size[u]=1; for(int i=h[u],s=0;i;i=nex[i]) { int v=to[i]; if(v==f)continue; dfs1(u,v); size[u]+=size[v]; if(size[v]>s)s=size[v],son[u]=v; }}int dfs2(int u){ dfn[u]=++cnt; int x=dfn[u]; a[x]=aa[u]; if(!top[u])top[u]=u; if(son[u]){top[son[u]]=top[u];dfs2(son[u]);} for(int i=h[u];i;i=nex[i]) { int v=to[i]; if(v==son[u]||v==fa[u])continue; x=max(x,dfs2(v)); } return x;}void bt(int no,int l,int r){ if(l==r){t[no]=a[l];return;} int mid=(l+r)>>1; bt(ls);bt(rs); t[no]=t[lc]+t[rc];}void pushdown(int no,int l,int r,int k){ t[no]+=k*(r-l+1); if(l!=r)lazy[lc]+=k,lazy[rc]+=k; lazy[no]=0;}void update(int no,int l,int r,int L,int R,int k){ if(l>=L&&r<=R){lazy[no]+=k;pushdown(no,l,r,lazy[no]);return;} if(lazy[no])pushdown(no,l,r,lazy[no]); if(r R)return; int mid=(l+r)>>1;update(ls,L,R,k);update(rs,L,R,k); t[no]=t[lc]+t[rc];}ll query(int no,int l,int r,int L,int R){ if(lazy[no])pushdown(no,l,r,lazy[no]);lazy[no]=0; if(l>=L&&r<=R)return t[no]%p; if(l>R||r >1; return (query(ls,L,R)+query(rs,L,R))%p;}int main(){ n=gi(),m=gi(),root=gi(),p=gi(); for(int i=1;i<=n;++i)aa[i]=gi(); for(int i=1,t=0;i dfn[y])swap(x,y); update(1,1,n,dfn[x],dfn[y],z); } if(op==2){ int x=gi(),y=gi();int s=0; while(top[x]!=top[y]) { if(dfn[x] dfn[y])swap(x,y); s+=query(1,1,n,dfn[x],dfn[y]); printf("%lld\n",s%p); } if(op==3){ int x=gi(),z=gi(); update(1,1,n,dfn[x],dfn[x]+size[x]-1,z); } if(op==4){ int x=gi(); printf("%lld\n",query(1,1,n,dfn[x],dfn[x]+size[x]-1)); } }}