刚学的好玩算法,AC2题,非常开心。
其实很早就有教过,以前以为很难就没有学,现在发现其实很简单也很有用。
更重要的是我很好调试,两题都是几乎一遍过的。
介绍树链剖分前,先确保已经学会以下基本技巧:
DFS序列,线段树/树状数组,LCA(最近公共祖先)
DFS序列确保你能听懂以下环节,线段树/树状数组是维护序列的有力工具,而LCA涉及树上的很多基本问题。
经常会遇到这样的题目:
对于一棵树,给x到y的路径上的点/边都做一个操作,并且查询x到y的路径上的点/边的值。
如果不是x到y的路径,而是节点x的子树,这可以方便地用DFS序列维护。
因为一棵子树在一个DFS序列中是连续的一段区间。
用类似的思想,我们尝试把树上的链也变成一段连续区间。
然而这是不可能的,因为链上是有分支的。
于是我们退而求其次,把一个链分成有限个区间,这边是有可能实现的,应运而生的算法就是树链剖分。
对于一棵树上的每个节点(x),我们定义(siz(x))为以(x)为根的子树的节点个数。
对于一棵树上的每一个非叶子节点,我们找到它的儿子们中(siz)最大的那一个,并且标记它到这个儿子的这条边。
那么对树上所有点都进行这个操作之后,被标记的边会形成若干条链,并且覆盖每一个节点。
我们把这样的边叫做重边,其他的边叫做轻边,把连续重边形成的链叫做重链,一个点也可以组成一条长度为1的重链。
定理①:每个点属于且只属于一个重链。
定理②:从一个点到根的路径中,重链个数不超过(O(log_2n))个,(n)表示树大小。
证明:从一个点向它的父亲走,如果这条边是轻边,那么子树的节点数至少翻倍,重链个数加一。
那么显然轻边个数不超过(O(log_2n)),而不是轻边的就组成重链,于是重链个数也不超过(O(log_2n))个。
好了,我们知道了重链的定义,性质,如何使用它呢?
因为一个点到根不超过(O(log_2n))条重链,那么任意两点间也不会超过(O(log_2n))条重链。
而每条重链都是一段连续的区间,那么链上问题就变成了区间问题的并了。
这就实现了链到区间的转化。
要想使用树链剖分,需要在代码中实现它,我们需要做些什么?
先考虑用使用重链,求出任意两点之间的路径吧,或者,求出它们的LCA。
考虑初始时(x,y)两点,我们发现每一条重链都能直接用它的最顶端的节点表示,记作(top)节点。
那么先看(x,y)属不属于同一条重链:(top(x)=top(y))?
只要不属于,一定要有一个跑出它的重链,实际上,必然是(top(x),top(y))中深度更深的那个跳出其重链。
不妨设其为(x),那么(x o y)的路径上就多了一条(top_x o x)的路径,之后执行(x=faz(top(x))),(faz(x))表示(x)的父亲节点。
不断重复这个操作,直到(top(x)=top(y)),那么这时(x,y)的LCA就是(x,y)中深度小的那个了。
这样的时间复杂度是(O(log_2n))的。
在做这些操作前,我们需要先求出(top(x))、(siz(x))、(dep(x))、(faz(x))等关键数组。
如何求出?其实一次DFS就够了。
DFS(u){ dep[u] = dep[faz[u]] + 1 Heavy = 0, siz[u] = 1 for ( i = u.sons ) { faz[i] = u DFS(i) siz[u] = siz[u] + siz[i] if ( siz[Heavy] < siz[i] ) Heavy = i } son[u] = Heavy for ( i = u.sons ) { if ( i != Heavy ) { j=i while ( j != NULL ) top[j] = i, j = son[j] } } }
这样就能处理好了,这里的(son[x])表示(x)的重儿子。
还有一个小问题就是:我们没有把树中的每个节点映射到序列中的某个编号上。
在通常的DFS序列中这个编号就是(dfn)数组,在树链剖分中,我们也可以使用(dfn)数组,但是要满足链上的点在一个区间中。
做到这一点也不难,直接在里面一样做就好,也不用第二个DFS。
问题在于,有的题目不止要你改链上的点,还要子树修改,而一棵树的总重链数是可以到(O(n))级别的,自然不能也用链做。
然而子树修改是个很简单的东西,只要满足子树内有序就好了,但是要同时满足的话……
也不是不可能,再写一个DFS就好啦。就和平常的求DFS序列的DFS一样,不过要注意先遍历到重儿子,这样可以保证重链上的点的编号连续。
总结:
树链剖分,DFS序列,本质上都是一样的,相当于一个树到数组的映射,把分支的结构变成连续的结构,并且满足一些性质。
DFS序列只要满足子树的节点是连续的即可,而树链剖分还要满足特定树链的节点连续。
这必然使树链剖分有更强的操作性,树链剖分是很重要的静态在线树上算法,通过预处理,一切操作都可以在线地完成。
接下来放两道例题:
直接的树链剖分模板,使用线段树维护区间。
// luogu-judger-enable-o2 #include<cstdio> #define F(i,a,b) for(int i=a;i<=(b);++i) #define F2(i,a,b) for(int i=a;i<(b);++i) #define eF(i,u) for(int i=h[u];i;i=nxt[i]) #define swap(a,b) tmp=a, a=b, b=tmp int n,q,R,P,a[100001],b[100001],tmp; int h[100001],to[200001],nxt[200001],tot; inline void ins(int x,int y){nxt[++tot]=h[x];to[tot]=y;h[x]=tot;} int top[100001],son[100001],siz[100001],dep[100001],faz[100001],dfn[100001],cnt; int dat[262145],laz[262145]; inline void Ex(int u){ for(int i=u;i;i=son[i]) top[i]=u; } void DFS(int u,int f){ siz[u]=1; dep[u]=dep[faz[u]]+1; int Heavy=0; eF(i,u) if(to[i]!=f){ DFS(to[i],faz[to[i]]=u); siz[u]+=siz[to[i]]; if(siz[Heavy]<siz[to[i]]) Heavy=to[i]; } son[u]=Heavy; eF(i,u) if(to[i]!=f) if(to[i]!=Heavy) Ex(to[i]); } void DFS2(int u,int f){ dfn[u]=++cnt; b[cnt]=a[u]; if(son[u]) DFS2(son[u],u); eF(i,u) if(to[i]!=f) if(to[i]!=son[u]) DFS2(to[i],u); } void build(int i,int l,int r){ if(l==r) {dat[i]=b[l]%P; return;} int mid=l+r>>1; build(i<<1,l,mid), build(i<<1|1,mid+1,r); dat[i]=(dat[i<<1]+dat[i<<1|1])%P; } inline void push_down(int i,int len){ int len2=len>>1; dat[i<<1]=(dat[i<<1]+(len-len2)*laz[i])%P; dat[i<<1|1]=(dat[i<<1|1]+len2*laz[i])%P; laz[i<<1]=(laz[i<<1]+laz[i])%P; laz[i<<1|1]=(laz[i<<1|1]+laz[i])%P; laz[i]=0; } void Add(int i,int l,int r,int a,int b,int x){ if(r<a||b<l) return; if(a<=l&&r<=b) {dat[i]=(dat[i]+x*(r-l+1))%P; laz[i]=(laz[i]+x)%P; return;} push_down(i,r-l+1); int mid=l+r>>1; Add(i<<1,l,mid,a,b,x); Add(i<<1|1,mid+1,r,a,b,x); dat[i]=(dat[i<<1]+dat[i<<1|1])%P; } int Qur(int i,int l,int r,int a,int b){ if(r<a||b<l) return 0; if(a<=l&&r<=b) return dat[i]; push_down(i,r-l+1); int mid=l+r>>1; return (Qur(i<<1,l,mid,a,b)+Qur(i<<1|1,mid+1,r,a,b))%P; } inline void Add(int x,int y,int z){ while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); Add(1,1,n,dfn[top[x]],dfn[x],z); x=faz[top[x]]; } if(dep[x]>dep[y]) swap(x,y); Add(1,1,n,dfn[x],dfn[y],z); } inline int Qur(int x,int y){ int ans=0; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); ans=(ans+Qur(1,1,n,dfn[top[x]],dfn[x]))%P; x=faz[top[x]]; } if(dep[x]>dep[y]) swap(x,y); return (ans+Qur(1,1,n,dfn[x],dfn[y]))%P; } int main(){ scanf("%d%d%d%d",&n,&q,&R,&P); F(i,1,n) scanf("%d",a+i); int x,y; F2(i,1,n) scanf("%d%d",&x,&y), ins(x,y), ins(y,x); dep[R]=1; DFS(R,0); Ex(R); DFS2(R,0); build(1,1,n); F(i,1,q){ int opt,x,y,z; scanf("%d",&opt); if(opt==1) scanf("%d%d%d",&x,&y,&z), Add(x,y,z); if(opt==2) scanf("%d%d",&x,&y), printf("%d ",Qur(x,y)); if(opt==3) scanf("%d%d",&x,&z), Add(1,1,n,dfn[x],dfn[x]+siz[x]-1,z); if(opt==4) scanf("%d",&x), printf("%d ",Qur(1,1,n,dfn[x],dfn[x]+siz[x]-1)); } return 0; }
NOI2015的题目,要在树上进行清零和置一的操作。有链上和子树上的操作。
#include<cstdio> #include<algorithm> #define F(i,a,b) for(int i=a;i<=(b);++i) #define F2(i,a,b) for(int i=a;i<(b);++i) #define eF(i,u) for(int i=h[u];i;i=nxt[i]) using namespace std; int n,q,a[100005],b[100005],tmp; int h[100005],to[100005],nxt[100005],tot; inline void ins(int x,int y){nxt[++tot]=h[x];to[tot]=y;h[x]=tot;} int top[100005],son[100005],siz[100005],dep[100005],faz[100005],dfn[100005],cnt; int dat[262145],laz[262145]; inline void Ex(int u){ for(int i=u;i;i=son[i]) top[i]=u; } void DFS(int u){ siz[u]=1; dep[u]=dep[faz[u]]+1; int Heavy=0; eF(i,u){ DFS(to[i]); siz[u]+=siz[to[i]]; if(siz[Heavy]<siz[to[i]]) Heavy=to[i]; } son[u]=Heavy; eF(i,u) if(to[i]!=Heavy) Ex(to[i]); } void DFS2(int u){ dfn[u]=++cnt; b[cnt]=a[u]; if(son[u]) DFS2(son[u]); eF(i,u) if(to[i]!=son[u]) DFS2(to[i]); } inline void push_down(int i,int len){ if(!laz[i]) return; int len2=len>>1; laz[i<<1]=laz[i<<1|1]=laz[i]; if(laz[i]==1) dat[i<<1]=dat[i<<1|1]=0; else dat[i<<1]=len-len2, dat[i<<1|1]=len2; laz[i]=0; } void Mdf(int i,int l,int r,int a,int b,int x){ if(r<a||b<l) return; if(a<=l&&r<=b) {dat[i]=x?r-l+1:0; laz[i]=x+1; return;} push_down(i,r-l+1); int mid=l+r>>1; Mdf(i<<1,l,mid,a,b,x); Mdf(i<<1|1,mid+1,r,a,b,x); dat[i]=dat[i<<1]+dat[i<<1|1]; } int Qur(int i,int l,int r,int a,int b){ if(r<a||b<l) return 0; if(a<=l&&r<=b) return dat[i]; push_down(i,r-l+1); int mid=l+r>>1; return Qur(i<<1,l,mid,a,b)+Qur(i<<1|1,mid+1,r,a,b); } inline void Mdf(int y){ int x=1; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); Mdf(1,1,n,dfn[top[x]],dfn[x],1); x=faz[top[x]]; } if(dep[x]>dep[y]) swap(x,y); Mdf(1,1,n,dfn[x],dfn[y],1); } inline int Qur(int y){ int x=1,ans=0; while(top[x]!=top[y]){ if(dep[top[x]]<dep[top[y]]) swap(x,y); ans=ans+Qur(1,1,n,dfn[top[x]],dfn[x]); x=faz[top[x]]; } if(dep[x]>dep[y]) swap(x,y); return ans+Qur(1,1,n,dfn[x],dfn[y]); } int main(){ scanf("%d",&n); int x; F2(i,1,n) scanf("%d",&x), ins(x+1,i+1), faz[i+1]=x+1; ++n; dep[1]=1; DFS(1); Ex(1); DFS2(1); scanf("%d",&q); F(i,1,q){ char s[10]; int x,y; scanf("%s%d",s,&x); ++x; if(s[0]=='i') y=Qur(x), Mdf(x), printf("%d ",Qur(x)-y); else printf("%d ",Qur(1,1,n,dfn[x],dfn[x]+siz[x]-1)), Mdf(1,1,n,dfn[x],dfn[x]+siz[x]-1,0); } return 0; }