zoukankan      html  css  js  c++  java
  • 树连剖分

    WPY神仙:什么时候能行云流水一次性过树链剖分啊!

    转载于luogu日报

    树链剖分就是将树分割成多条链,然后利用数据结构(线段树、树状数组等)来维护这些链。别说你不知道什么是树~~ ╮(─▽─)╭

    前置知识

    • dfs序
    • LCA 线段树

    先来回顾两个问题:

    1.将树从(x)(y)结点最短路径上所有节点的值都加上z

    这也是个模板题了吧

    我们很容易想到,树上差分可以以(O(n+m))的优秀复杂度解决这个问题

    2.求树从(x)(y)结点最短路径上所有节点的值之和

    lca大水题,我们又很容易地想到,dfs(O(n))预处理每个节点的dis(即到根节点的最短路径长度)

    然后对于每个询问,求出x,y两点的lca,利用lca的性质 distance $(x,y)=dis(x)+dis(y)-2*dis(lca) $求出结果

    时间复杂度(O(mlogn+n))

    现在来思考一个bug:

    如果刚才的两个问题结合起来,成为一道题的两种操作呢?

    刚才的方法显然就不够优秀了(每次询问之前要跑dfs更新dis)

    树剖是通过轻重边剖分将树分割成多条链,然后利用数据结构来维护这些链(本质上是一种优化暴力)

    首先明确概念:

    • 重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;
    • 轻儿子:父亲节点中除了重儿子以外的儿子;
    • 重边:父亲结点和重儿子连成的边;
    • 轻边:父亲节点和轻儿子连成的边;
    • 重链:由多条重边连接而成的路径;
    • 轻链:由多条轻边连接而成的路径;

    比如上面这幅图中,用黑线连接的结点都是重结点,其余均是轻结点,

    2-11就是重链,2-5就是轻链,用红点标记的就是该结点所在重链的起点,也就是下文提到的top结点,

    还有每条边的值其实是进行dfs时的执行序号。

    声明变量:

    int cnt,lnk[maxn],nxt[maxn<<1],to[maxn<<1];//建边
    int a[maxn<<2],lazy[maxn<<2];//线段树
    int son[maxn],w[maxn],id[maxn],tot,deep[maxn],size[maxn],top[maxn],fa[maxn],rk[maxn];//son[maxn]:重儿子,w[maxn]:权值,id[maxn]:树上节点对应线段树上的标号,deep[maxn]:节点深度,size[maxn]节点子树大小,top[maxn]链最上端的编号,fa[maxn]父节点,rk[maxn]线段树上编号对应的树上节点
    

    树链剖分的实现

    1.对于一个点我们首先求出它所在的子树大小,找到它的重儿子(即处理出size,son数组)

    解释:比如说点1,它有三个儿子2,3,4

    2所在子树的大小是5

    3所在子树的大小是2

    4所在子树的大小是6

    那么1的重儿子是4

    ps:如果一个点的多个儿子所在子树大小相等且最大,那随便找一个当做它的重儿子就好了。

    叶节点没有重儿子,非叶节点有且只有一个重儿子

    2.在dfs过程中顺便记录其父亲以及深度(即处理出f,d数组),操作1,2可以通过一遍dfs完成

    inline void DFS_1(int x,int f,int dep){
    	deep[x]=dep;fa[x]=f;size[x]=1;
    	int max_son=-1;
    	for(int j=lnk[x];j;j=nxt[j]){
    		if(to[j]==f)continue;
    		DFS_1(to[j],x,dep+1);
    		size[x]+=size[to[j]];
    		if(size[to[j]]>max_son){max_son=size[to[j]],son[x]=to[j];}
    	}
    	return ;
    }
    


    dfs跑完大概是这样的,大家可以手动模拟一下

    3.第二遍dfs,然后连接重链,同时标记每一个节点的dfs序,并且为了用数据结构来维护重链,我们在dfs时保证一条重链上各个节点dfs序连续(即处理出数组top,id,rk)

    inline void DFS_2(int x,int tp){
    	id[x]=++tot;
    	rk[tot]=x;
    	top[x]=tp;
    	if(son[x])DFS_2(son[x],tp);
    	for(int j=lnk[x];j;j=nxt[j]){
    		if(to[j]==fa[x]||to[j]==son[x])continue;
    		DFS_2(to[j],to[j]);
    	}
    	return ;
    }
    

    dfs跑完大概是这样的,大家可以手动模拟一下

    4,两遍dfs就是树链剖分的主要处理,通过dfs我们已经保证一条重链上各个节点dfs序连续,那么可以想到,我们可以通过数据结构(以线段树为例)来维护一条重链的信息

    回顾上文的那个题目,修改和查询操作原理是类似的,以查询操作为例,其实就是个LCA,不过这里使用了top来进行加速,因为top可以直接跳转到该重链的起始结点,轻链没有起始结点之说,他们的top就是自己。需要注意的是,每次循环只能跳一次,并且让结点深的那个来跳到top的位置,避免两个一起跳从而插肩而过。

    inline void build(int x,int L,int R){
    	if(L==R){
    		a[x]=w[rk[L]]%TT;
    		return ;
    	}
    	int mid=(R-L>>1)+L;
    	build(x<<1,L,mid);
    	build(x<<1|1,mid+1,R);
    	a[x]=(a[x<<1]+a[x<<1|1])%TT;
    	return ;
    }
    
    inline void pushdown(int x,int len){
    	if(lazy[x]==0)return ;
    	lazy[x<<1]=(lazy[x<<1]+lazy[x])%TT;
    	lazy[x<<1|1]=(lazy[x<<1|1]+lazy[x])%TT;
    	a[x<<1]=(a[x<<1]+lazy[x]*(len-(len>>1)))%TT;
    	a[x<<1|1]=(a[x<<1|1]+lazy[x]*(len>>1))%TT;
    	lazy[x]=0;
    	return ;
    }
    
    inline void query(int x,int l,int r,int L,int R){
    	if(L<=l&&r<=R){ret=(ret+a[x])%TT;return ;}
    	else {
    		pushdown(x,r-l+1);int mid=(r-l>>1)+l;
    		if(L<=mid)query(x<<1,l,mid,L,R);
    		if(R>mid)query(x<<1|1,mid+1,r,L,R);
    	}
    	return ;
    }
    
    inline void update(int x,int l,int r,int L,int R,int k){
    	if(L<=l&&r<=R){
    		lazy[x]=(lazy[x]+k)%TT;
    		a[x]=(a[x]+k*(r-l+1))%TT;
    	}
    	else{
    		pushdown(x,r-l+1);
    		int mid=(r-l>>1)+l;
    		if(L<=mid)update(x<<1,l,mid,L,R,k);
    		if(R>mid)update(x<<1|1,mid+1,r,L,R,k);
    		a[x]=(a[x<<1]+a[x<<1|1])%TT;
    	}
    	return ;
    }
    
    inline int qRange(int x,int y){
    	int ans=0;
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		ret=0;
    		query(1,1,N,id[top[x]],id[x]);
    		ans=(ret+ans)%TT;
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	ret=0;query(1,1,N,id[x],id[y]);
    	ans=(ans+ret)%TT;
    	return ans;
    }
    
    inline void upRange(int x,int y,int k){
    	k%=TT;
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		update(1,1,N,id[top[x]],id[x],k);
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	update(1,1,N,id[x],id[y],k);
    	return ;
    }
    
    inline int qson(int x){
    	ret=0;
    	query(1,1,N,id[x],id[x]+size[x]-1);
    	return ret;
    }
    inline void upson(int x,int k){
    	update(1,1,N,id[x],id[x]+size[x]-1,k);
    }
    

    大家如果明白了树链剖分,也应该有举一反三的能力(反正我没有),修改和LCA就留给大家自己完成了

    5.树链剖分的时间复杂度

    树链剖分的两个性质:

    1,如果(u,v)是一条轻边,那么(size(v)<size(u)/2)

    2,从根结点到任意结点的路所经过的轻重链的个数必定都小于(logn)

    可以证明,树链剖分的时间复杂度为(O(nlogn))

    例题

    1.树链剖分模板

    code

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn=100005;
    typedef long long LL;
    int N,M,R,TT;
    int  ret;
    inline int read(){
    	int ret=0,f=1;char ch=getchar();
    	while(ch<'0'|ch>'9'){if(ch=='-')f=-f;ch=getchar();}
    	while(ch<='9'&&ch>='0')ret=ret*10+ch-'0',ch=getchar();
    	return ret*f;
    }
    int cnt,lnk[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn];
    int a[maxn<<2],lazy[maxn<<2];
    int son[maxn],id[maxn],tot,deep[maxn],size[maxn],top[maxn],fa[maxn],rk[maxn];
    
    inline void add_e(int x,int y){to[++cnt]=y;nxt[cnt]=lnk[x];lnk[x]=cnt;}
    
    inline void DFS_1(int x,int f,int dep){
    	deep[x]=dep;fa[x]=f;size[x]=1;
    	int max_son=-1;
    	for(int j=lnk[x];j;j=nxt[j]){
    		if(to[j]==f)continue;
    		DFS_1(to[j],x,dep+1);
    		size[x]+=size[to[j]];
    		if(size[to[j]]>max_son){max_son=size[to[j]],son[x]=to[j];}
    	}
    	return ;
    }
    
    inline void DFS_2(int x,int tp){
    	id[x]=++tot;
    	rk[tot]=x;
    	top[x]=tp;
    	if(son[x])DFS_2(son[x],tp);
    	for(int j=lnk[x];j;j=nxt[j]){
    		if(to[j]==fa[x]||to[j]==son[x])continue;
    		DFS_2(to[j],to[j]);
    	}
    	return ;
    }
    
    inline void build(int x,int L,int R){
    	if(L==R){
    		a[x]=w[rk[L]]%TT;
    		return ;
    	}
    	int mid=(R-L>>1)+L;
    	build(x<<1,L,mid);
    	build(x<<1|1,mid+1,R);
    	a[x]=(a[x<<1]+a[x<<1|1])%TT;
    	return ;
    }
    
    inline void pushdown(int x,int len){
    	if(lazy[x]==0)return ;
    	lazy[x<<1]=(lazy[x<<1]+lazy[x])%TT;
    	lazy[x<<1|1]=(lazy[x<<1|1]+lazy[x])%TT;
    	a[x<<1]=(a[x<<1]+lazy[x]*(len-(len>>1)))%TT;
    	a[x<<1|1]=(a[x<<1|1]+lazy[x]*(len>>1))%TT;
    	lazy[x]=0;
    	return ;
    }
    
    inline void query(int x,int l,int r,int L,int R){
    	if(L<=l&&r<=R){ret=(ret+a[x])%TT;return ;}
    	else {
    		pushdown(x,r-l+1);int mid=(r-l>>1)+l;
    		if(L<=mid)query(x<<1,l,mid,L,R);
    		if(R>mid)query(x<<1|1,mid+1,r,L,R);
    	}
    	return ;
    }
    
    inline void update(int x,int l,int r,int L,int R,int k){
    	if(L<=l&&r<=R){
    		lazy[x]=(lazy[x]+k)%TT;
    		a[x]=(a[x]+k*(r-l+1))%TT;
    	}
    	else{
    		pushdown(x,r-l+1);
    		int mid=(r-l>>1)+l;
    		if(L<=mid)update(x<<1,l,mid,L,R,k);
    		if(R>mid)update(x<<1|1,mid+1,r,L,R,k);
    		a[x]=(a[x<<1]+a[x<<1|1])%TT;
    	}
    	return ;
    }
    
    inline int qRange(int x,int y){
    	int ans=0;
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		ret=0;
    		query(1,1,N,id[top[x]],id[x]);
    		ans=(ret+ans)%TT;
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	ret=0;query(1,1,N,id[x],id[y]);
    	ans=(ans+ret)%TT;
    	return ans;
    }
    
    inline void upRange(int x,int y,int k){
    	k%=TT;
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		update(1,1,N,id[top[x]],id[x],k);
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	update(1,1,N,id[x],id[y],k);
    	return ;
    }
    
    inline int qson(int x){
    	ret=0;
    	query(1,1,N,id[x],id[x]+size[x]-1);
    	return ret;
    }
    inline void upson(int x,int k){
    	update(1,1,N,id[x],id[x]+size[x]-1,k);
    }
    int main(){
    	N=read();M=read();R=read();TT=read();
    	for(int i=1;i<=N;i++)w[i]=read();
    	for(int i=1;i<N;i++){
    		int x=read(),y=read();
    		add_e(x,y);add_e(y,x);
    	}
    	DFS_1(R,0,1);
    	DFS_2(R,R);
    	build(1,1,N);
    	for(int i=1;i<=M;i++){
    		int p,x,y,k;
    		p=read();
    		if(p==1){x=read(),y=read(),k=read();upRange(x,y,k);}
    		if(p==2){x=read();y=read();printf("%d
    ",qRange(x,y));}
    		if(p==3){x=read();k=read();upson(x,k);}
    		if(p==4){x=read();printf("%d
    ",qson(x));}
    	}
    	return 0;
    }
    

    WPY神仙:什么时候能行云流水一次性过树链剖分啊!

    前后呼应

  • 相关阅读:
    java之获取变量的类型
    java中的++和--
    java(三)基础类型之间的转换
    golang数据结构之总结
    golang数据结构之树的三种遍历方式
    golang数据结构之散哈希表(Hash)
    golang数据结构之递归解决迷宫问题
    golang数据结构之利用栈求计算表达式(加减乘除)
    golang数据结构之栈
    golang数据结构之快速排序
  • 原文地址:https://www.cnblogs.com/martian148/p/13376464.html
Copyright © 2011-2022 走看看