树链剖分
总是有出题人喜欢把序列上用线段树解决的题目出到树上,让选手强行写个树链剖分或树分治或某种动态树数据结构。
这种行为已经很无趣了。
所以我们想让大家知道,不光可以放在静态树上,动态仙人掌也是可以的。
以上都是在扯淡。
所以现在还是讲讲树链剖分吧。常见的树链剖分分为三种(或许我知道的太少?),最广为人知的是重链剖分,另一种是长链剖分,还有一种是实链剖分(一般在LCT中有应用)。
由于日常使用时常把树链剖分和重链剖分等同起来,在本博客中,若无特殊声明也视这两个概念为同一个概念。
基本概念啊
树链剖分是指以某种规则将树剖成若干条不相交的链。以子树大小为剖分规则的就叫重链剖分,以子树深度为剖分规则的就叫长链剖分。
每个非叶节点都有且仅有一个重儿子(长链剖分选的也叫重儿子吧,毕竟长儿子听起来不习惯),其余的子节点就叫轻儿子。
连接父节点和重儿子的边就叫重边,其余的边就叫轻边,重边连成的链就叫重链。
重链剖分吧
常用于求LCA,以及满足毒瘤出题人的邪恶想法(见开头)。
学重链剖分这种东西,看图其实没太大必要,我摆出代码,你明确每一步要做的事就可以理解了。
我现在的代码已经是经过多次修改后的优秀版本了,理解起来很方便,作为代码参考也是个不错的选择(如果你实在想看图请转神仙Itst的blog)。
但是在此之前我们必须明确每个数组的意义:
h[](head),s[](subsequence),g[](goal)//链式前向星数组
t[](total)//子树大小
p[](parent)//父节点
q[](/*为了和p对称*/)//重儿子
r[](root)//所在重链的顶端
w[](weight)//点权
f[](function)//树上节点到序列上的映射
u[](/*为了与w对应*/)//映射到序列上之后的点权
dfs1:确定深度、父节点,预处理子树大小、确定重儿子
void dfs1(int x,int f){d[x]=d[f]+1,p[x]=f,t[x]=1;
for(R int i=h[x],y,m=0;i;i=s[i])
if((y=g[i])^f){dfs1(y,x),t[x]+=t[y];
if(t[y]>m) m=t[y],q[x]=y;
}
}
dfs2:剖出重链(确定链顶节点),把树上结点对应到序列上(如果你只需要求个LCA就不需要这步)
void dfs2(int x,int t){f[x]=++e,u[e]=w[x],r[x]=t;
if(!q[x]) return ; dfs2(q[x],t);
for(R int i=h[x],y;i;i=s[i])
if((y=g[i])^p[x]&&y^q[x]) dfs2(y,y);
}
如果你只想求LCA:如果x和y不在一条重链上,就把链顶深度大的直接跳到链顶,到了一条重链上之后,LCA就是深度小的点。
I int lca(int x,int y){
for(;r[x]^r[y];x=p[r[x]]) if(d[r[x]]<d[r[y]]) swap(x,y);
return d[x]<d[y]? x:y;
}
但是如果你认为树剖只能求个LCA就大错特错了,剖出来的一条重链、一棵子树映射到序列的编号都是连续的(这个结论请自己手玩),那么我们就可以把题目要求的修改和询问放在线段树上进行。
一般来讲,这样做的时间复杂度是\(O(n(logn)^2)\)的。没什么值得特别讲的,看一道例题。(如果你还不熟悉线段树的操作或者不知道像下面这样写有什么好处,可以看看我的线段树总结)
洛谷P3384【模板】树链剖分
该讲的刚刚都讲了,直接上代码,建议初学者照着题解先打一遍(如果你觉得我的代码太丑了,洛谷上还有很多),然后再自己去做题。
#include<cstdio>
#include<cctype>
#define R register
#define I inline
using namespace std;
const int S=100003,N=200003,M=400003;
char buf[S],*p1,*p2;
I char gc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,S,stdin),p1==p2)?EOF:*p1++;}
I int rd(){
R int f=0; R char c=gc();
while(c<48||c>57) c=gc();
while(c>47&&c<58) f=f*10+(c^48),c=gc();
return f;
}
int h[S],g[N],s[N],w[S],f[S],u[S],d[S],p[S],q[S],t[S],r[S],a[M],b[M],c,e,n,mod;
I void swap(int &x,int &y){x^=y,y^=x,x^=y;}
I void add(int x,int y){s[++c]=h[x],h[x]=c,g[c]=y;}
void dfs1(int x,int f){d[x]=d[f]+1,p[x]=f,t[x]=1;
for(R int i=h[x],y,m=0;i;i=s[i])
if((y=g[i])^f){dfs1(y,x),t[x]+=t[y];
if(t[y]>m) m=t[y],q[x]=y;
}
}
void dfs2(int x,int t){f[x]=++e,u[e]=w[x],r[x]=t;
if(!q[x]) return ; dfs2(q[x],t);
for(R int i=h[x],y;i;i=s[i])
if((y=g[i])^p[x]&&y^q[x]) dfs2(y,y);
}
I void upd(int k,int l,int r,int z){a[k]+=z*(r-l+1),b[k]+=z;}
I void psu(int k,int p,int q){a[k]=(a[p]+a[q])%mod;}
I void psd(int k,int l,int r){
if(!b[k]) return ;
R int p=k<<1,q=p|1,m=l+r>>1,z=b[k];
upd(p,l,m,z),upd(q,m+1,r,z),b[k]=0;
}
void bld(int k,int l,int r){
if(l==r){a[k]=u[l]; return ;}
R int p=k<<1,q=p|1,m=l+r>>1;
bld(p,l,m),bld(q,m+1,r),psu(k,p,q);
}
void mdf(int k,int l,int r,int x,int y,int z){
if(x<=l&&r<=y){upd(k,l,r,z); return ;}
R int p=k<<1,q=p|1,m=l+r>>1; psd(k,l,r);
if(x<=m) mdf(p,l,m,x,y,z);
if(m<y) mdf(q,m+1,r,x,y,z);
psu(k,p,q);
}
int qry(int k,int l,int r,int x,int y){
if(x<=l&&r<=y) return a[k];
R int p=k<<1,q=p|1,m=l+r>>1,o=0; psd(k,l,r);
if(x<=m) (o+=qry(p,l,m,x,y))%=mod;
if(m<y) (o+=qry(q,m+1,r,x,y))%=mod;
return o;
}
I void mdf1(int x,int y,int z){
while(r[x]^r[y]){
if(d[r[x]]<d[r[y]]) swap(x,y);
mdf(1,1,n,f[r[x]],f[x],z),x=p[r[x]];
}
if(d[x]>d[y]) swap(x,y);
mdf(1,1,n,f[x],f[y],z);
}
I int qry1(int x,int y){
R int o=0;
while(r[x]^r[y]){
if(d[r[x]]<d[r[y]]) swap(x,y);
(o+=qry(1,1,n,f[r[x]],f[x]))%=mod,x=p[r[x]];
}
if(d[x]>d[y]) swap(x,y);
return (o+qry(1,1,n,f[x],f[y]))%mod;
}
I void mdf2(int x,int z){mdf(1,1,n,f[x],f[x]+t[x]-1,z);}
I int qry2(int x){return qry(1,1,n,f[x],f[x]+t[x]-1);}
int main(){
n=rd(); R int m=rd(),r=rd(),i,k,x,y,z; mod=rd();
for(i=1;i<=n;++i) w[i]=rd();
for(i=1;i<n;++i) x=rd(),y=rd(),add(x,y),add(y,x);
dfs1(r,0),dfs2(r,r),bld(1,1,n);
while(m--){k=rd(),x=rd();
if(k==1) y=rd(),z=rd(),mdf1(x,y,z);
if(k==2) y=rd(),printf("%d\n",qry1(x,y));
if(k==3) z=rd(),mdf2(x,z);
if(k==4) printf("%d\n",qry2(x));
}
return 0;
}
推荐一些基础题(可以按给出的顺序做题):
P2590 [ZJOI2008]树的统计
P2146 [NOI2015]软件包管理器
P3258 [JLOI2014]松鼠的新家
P3178 [HAOI2015]树上操作
P4315 月下“毛景树”(维护边权的模板)
P4114 Qtree1
P4116 Qtree3
P2486 [SDOI2011]染色 题解
几道较难题:
CF1017G The Tree 题解
P3613 睡觉困难综合征 题解
长链剖分哦
还是放链接吧。
实链剖分嘛
直接看LCT总结吧。
总结呢
重链剖分不要什么脑子,变来变去基本还是线段树的操作在变,只要会在序列上操作,就不难想出树上的操作;长链剖分就是看见下标是深度直接套板子,想到转移方程就能切;实链剖分就更不要讲,LCT全是板子题。。。