树链剖分回忆笔记
摘抄定义:
- 重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;
- 轻儿子:父亲节点中除了重儿子以外的儿子;
- 重边:父亲结点和重儿子连成的边;
- 轻边:父亲节点和轻儿子连成的边;
- 重链:由多条重边连接而成的路径;
- 轻链:由多条轻边连接而成的路径;
然后就没有然后了
所有有关于“轻”字的东西都没啥用
对着图理解会好一点
标红点点的是一条重链的起点
我们发现,1、2、3、4、8、10、11号边都是重边
更重要的是,所有节点都在一个重链上(一个结点的也算)
dfs序在树链剖分中的定义也有一点不一样
由于为了维护方便,我们把一条链上节点的DFS序连续
树链剖分大名鼎鼎的两个dfs就在预处理dfs序、深度、轻重儿子、轻重链、子树大小
然后树链剖分的主要思想就是对重链进行各种高级数据结构维护,例如线段树、平衡树......
好吧,就这样
板子题代码:
#include<bits/stdc++.h> using namespace std; const int N=200005; long long n,m,r,p;//树的结点个数、操作个数、根节点序号和取模数 long long val[N];//每个节点的初始权值 long long cnt=0,hed[N],tal[N*2],nxt[N*2]; long long f[N];//父亲节点 long long d[N];//节点深度 long long size[N];//子树节点个数 long long s[N]={0};//重儿子编号 long long id[N];//DFS序 long long Rk[N];//该dfs序在原树中对应的编号 long long T[N];//链顶 long long tot=0;//时间戳 struct ST{ long long value; long long LZT; }t[N<<2];//线段树 void addege(long long a,long long b){ cnt++; tal[cnt]=b; nxt[cnt]=hed[a]; hed[a]=cnt; } void dfs1(long long u,long long fa,long long dis){//第一遍dfs d[u]=dis;//节点初始化 f[u]=fa; size[u]=1; for(long long i=hed[u];i;i=nxt[i]){ long long v=tal[i]; if(v==fa) continue; dfs1(v,u,dis+1);//遍历儿子 size[u]+=size[v];//更新子树节点个数 if(s[u]==0) s[u]=v; else if(size[s[u]]<size[v]) s[u]=v;//选择重儿子 } } void dfs2(long long u,long long gf){//当前节点、链顶 T[u]=gf; id[u]=++tot; Rk[tot]=u; if(!s[u]) return; dfs2(s[u],gf); for(long long i=hed[u];i;i=nxt[i]){ long long v=tal[i]; if(v==f[u]) continue; if(v==s[u]) continue; dfs2(v,v); } } long long len(long long l,long long r){return r-l+1;} void push_up(long long num){ t[num].value=(t[num<<1].value+t[num<<1|1].value)%p; } void push_down(long long l,long long r,long long num){ if(t[num].LZT){ long long mid=(l+r)>>1; t[num<<1].LZT+=t[num].LZT; t[num<<1|1].LZT+=t[num].LZT; t[num<<1].value+=len(l,mid)*t[num].LZT; t[num<<1].value%=p; t[num<<1|1].value+=len(mid+1,r)*t[num].LZT; t[num<<1|1].value%=p; t[num].LZT=0; } } void build(long long l,long long r,long long num){ if(l==r){ t[num].LZT=0; t[num].value=val[Rk[l]]%p; return; } long long mid=(l+r)>>1; build(l,mid,num<<1),build(mid+1,r,num<<1|1); push_up(num); } void upt(long long l,long long r,long long num,long long L,long long R,long long X){ if(l>=L&&r<=R){ t[num].LZT+=X; t[num].LZT%=p; t[num].value+=len(l,r)*X; t[num].value%=p; return; } if(l>R||r<L) return; long long mid=(l+r)>>1; push_down(l,r,num); upt(l,mid,num<<1,L,R,X); upt(mid+1,r,num<<1|1,L,R,X); push_up(num); } long long qus(long long l,long long r,long long num,long long L,long long R){ if(l>=L&&r<=R){ return t[num].value%p; } if(l>R||r<L) return 0; long long mid=(l+r)>>1; push_down(l,r,num); return (qus(l,mid,num<<1,L,R)+qus(mid+1,r,num<<1|1,L,R))%p; } long long ask(long long x,long long y){ long long sum=0; while(T[x]!=T[y]){ if(d[T[x]]<d[T[y]]) swap(x,y); sum+=qus(1,n,1,id[T[x]],id[x]); sum%=p; x=f[T[x]]; } if(d[x]>d[y]) swap(x,y); return (sum+qus(1,n,1,id[x],id[y]))%p; } void pus(long long x,long long y,long long z){ while(T[x]!=T[y]){ if(d[T[x]]<d[T[y]]) swap(x,y); upt(1,n,1,id[T[x]],id[x],z); x=f[T[x]]; } if(d[x]>d[y]) swap(x,y); upt(1,n,1,id[x],id[y],z); } int main(){ scanf("%lld%lld%lld%lld",&n,&m,&r,&p); for(long long i=1;i<=n;i++){ scanf("%lld",&val[i]); } for(long long i=1;i<n;i++){ long long a,b; scanf("%lld%lld",&a,&b); addege(a,b); addege(b,a); } dfs1(r,-1,1);//从root出发,求出每个节点的深度、子树节点个数、父亲编号以及重儿子 // ↑ // 节点,父亲节点,深度 dfs2(r,r);//第二遍dfs求出其余信息 //-------------------------------------------------------------------------- //接下来进入线段树部分! build(1,n,1);//建树 while(m--){ long long opt; scanf("%lld",&opt); if(opt==1){ long long x,y,z; scanf("%lld%lld%lld",&x,&y,&z); z%=p; pus(x,y,z); } else if(opt==2){ long long x,y; scanf("%lld%lld",&x,&y); printf("%lld\n",ask(x,y)%p); } else if(opt==3){ long long x,y; scanf("%lld%lld",&x,&y); y%=p; upt(1,n,1,id[x],id[x]+size[x]-1,y); } else{ long long x; scanf("%lld",&x); printf("%lld\n",qus(1,n,1,id[x],id[x]+size[x]-1)%p); } } return 0; }