zoukankan      html  css  js  c++  java
  • 树链剖分原理与应用

    树链剖分这个算法我看了好多大神们的博客,才慢慢领悟,希望我写的过得去( ·  · )

    众所周知,维护区间信息的题目可以用线段树高效实现。类似于区间这样一维的结构,在树上维护两点间的信息【比如树上两点之间的最大值】也可以用线段树吗?
    仔细想想,好像很难实现。因为线段树维护的是链状结构,而树是一张图。
    但我们想想,如果能把一棵树化为一条链,使得要节点间编号是一段一段的【也就是有些节点间的编号是连续的,但不是所有节点间都连续】,这样就可以通过多次调用维护区间的线段树来实现线段树维护树【两点之间有几个连续的段就调用几次】

    这就是树链剖分要做的事情:把树分成许多条链,对节点重新编号,化树为链,以套用其他链状数据结构

    怎么剖?


    最好的就是剖分轻重链。

    首先,我们先维护一些节点的信息:

    siz[u]      以u为根的子树的大小
    fa[u]       u的父亲节点
    dep[u]    u的深度
    son[u]    u的重儿子
    top[u]     u所在重链的顶端节点
    id[u]       树链剖分后u节点的编号

    对于每一个非叶节点,它所有儿子中siz最大的那个就是它的重儿子。
    对于前4项,我们可以通过一次dfs完成
    看代码:
    void dfs1(int u,int d,int f){
    	int to;
    	fa[u]=f;
    	dep[u]=++d;
    	siz[u]=1;
    	for(int k=head[u];k!=-1;k=edge[k].next)
    		if((to=edge[k].to)!=f){
    			dfs1(to,d,u);
    			siz[u]+=siz[to];
    			if(!son[u]||siz[son[u]]<siz[to]) son[u]=to;
    		}
    	
    }

    对于top和id,我们需要再进行一次dfs,这次dfs优先往重儿子,这样子dfs序就是id,因为这样你会发现:
    1、对于每个重链,它上边的节点编号一定是连续的
    2、对于每个节点,以它为根的子树里的所有节点一定是它接下来的那些编号【比如一个节点编号2,它为根的子树大小6,那么接下来3、4、5、6、7一定在它的子树里】
    这两个性质有什么用呢?
    当我们要维护两节点间的信息时,我们只需沿着重链就可以套用线段树了【因为编号是连续的】(性质1)
    当我们要维护某一个子树信息时,我们只需维护区间[id[u],id[u]+siz[u]-1](性质2)

    具体实现:
    void dfs2(int u,int f,int flag){
    	id[u]=++cnt;Hash[cnt]=u;
    	flag ? top[u]=top[f]:top[u]=u;
    	if(son[u]) dfs2(son[u],u,1);
    	int to;
    	for(int k=head[u];k!=-1;k=edge[k].next){
    		if((to=edge[k].to)!=f&&to!=son[u])
    			dfs2(to,u,0);
    	}
    }

    就这样子,我们就“剖”完了(^_^)
    代码还是很容易理解的,多打打

    树链剖分求LCA &树链剖分+线段树

    为什么放在一起?因为这两个玩意原理一样。
    对于节点u和v,它们在一个重链里,当且仅当top[u]==top[v]成立
    若它们不在一个重链,我们不妨设top[u]的深度较大,那么我们令u=fa[top[u]],继续往上找。
    在寻找过程中经过的路径就是u和v之间的路径,u最后到达的节点就是lca

    这里就不在赘述了【我懒。。。】

    拍上一个树链剖分模板题:

    洛谷P3384 【模板】树链剖分

    题目描述

    如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

    操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z

    操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和

    操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z

    操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

    输入输出格式

    输入格式:

    第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。

    接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。

    接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)

    接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:

    操作1: 1 x y z

    操作2: 2 x y

    操作3: 3 x z

    操作4: 4 x

    输出格式:

    输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模

    输入输出样例

    输入样例#1:
    5 5 2 24
    7 3 7 8 0 
    1 2
    1 5
    3 1
    4 1
    3 4 2
    3 2 2
    4 5
    1 5 1 3
    2 1 3
    输出样例#1:
    2
    21

    说明

    时空限制:1s,128M

    数据规模:

    对于30%的数据: N≤10,M≤10 N leq 10, M leq 10 N10,M10

    对于70%的数据: N≤103,M≤103 N leq {10}^3, M leq {10}^3 N103,M103

    对于100%的数据: N≤105,M≤105 N leq {10}^5, M leq {10}^5 N105,M105

    经典的模板题,直接拍代码,慢慢体会:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int maxn=100005,INF=200000000;
    
    inline int read(){
    	int out=0,flag=1;char c=getchar();
    	while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();}
    	while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();}
    	return out*flag;
    }
    
    int N,M,rt,P;
    int A[maxn];
    
    int head[maxn],nedge=0;
    struct EDGE{
    	int to,next;
    }edge[2*maxn];
    
    inline void build(int a,int b){
    	edge[nedge]=(EDGE){b,head[a]};
    	head[a]=nedge++;
    	edge[nedge]=(EDGE){a,head[b]};
    	head[b]=nedge++;
    }
    
    int siz[maxn],fa[maxn],son[maxn],dep[maxn],top[maxn],id[maxn],Hash[maxn],cnt=0;
    
    void dfs1(int u,int d,int f){
    	int to;
    	fa[u]=f;
    	dep[u]=++d;
    	siz[u]=1;
    	for(int k=head[u];k!=-1;k=edge[k].next)
    		if((to=edge[k].to)!=f){
    			dfs1(to,d,u);
    			siz[u]+=siz[to];
    			if(!son[u]||siz[son[u]]<siz[to]) son[u]=to;
    		}
    	
    }
    
    void dfs2(int u,int f,int flag){
    	id[u]=++cnt;Hash[cnt]=u;
    	flag ? top[u]=top[f]:top[u]=u;
    	if(son[u]) dfs2(son[u],u,1);
    	int to;
    	for(int k=head[u];k!=-1;k=edge[k].next){
    		if((to=edge[k].to)!=f&&to!=son[u])
    			dfs2(to,u,0);
    	}
    }
    
    int L,R,sum[4*maxn],lazy[4*maxn];
    
    void build(int u,int l,int r){
    	if(l==r) sum[u]=A[Hash[l]];
    	else{
    		int mid=(l+r)>>1;
    		build(u<<1,l,mid);
    		build(u<<1|1,mid+1,r);
    		sum[u]=(sum[u<<1]+sum[u<<1|1])%P;
    	}
    }
    
    void pd(int u,int l,int r){
    	int mid=(l+r)>>1;
    	sum[u<<1]=(sum[u<<1]+lazy[u]*(mid-l+1)%P)%P;
    	sum[u<<1|1]=(sum[u<<1|1]+lazy[u]*(r-mid)%P)%P;
    	lazy[u<<1]=(lazy[u<<1]+lazy[u])%P;
    	lazy[u<<1|1]=(lazy[u<<1|1]+lazy[u])%P;
    	lazy[u]=0;
    }
    
    void add(int u,int l,int r,int v){
    	if(l>=L&&r<=R) {sum[u]=(sum[u]+(r-l+1)*v%P)%P;lazy[u]=(lazy[u]+v)%P;}
    	else{
    		if(lazy[u]) pd(u,l,r);
    		int mid=(l+r)>>1;
    		if(mid>=L) add(u<<1,l,mid,v);
    		if(mid<R) add(u<<1|1,mid+1,r,v);
    		sum[u]=(sum[u<<1]+sum[u<<1|1])%P;
    	}
    }
    
    int Query(int u,int l,int r){
    	if(l>=L&&r<=R) return sum[u];
    	else{
    		if(lazy[u]) pd(u,l,r);
    		int mid=(l+r)>>1;
    		if(mid>=R) return Query(u<<1,l,mid);
    		else if(mid<L) return Query(u<<1|1,mid+1,r);
    		else return (Query(u<<1,l,mid)+Query(u<<1|1,mid+1,r))%P;
    	}
    }
    
    void solve1(int u,int v,int x){
    	while(top[u]!=top[v]){
    		if(dep[top[u]]<dep[top[v]]) swap(u,v);
    		L=id[top[u]];
    		R=id[u];
    		add(1,1,N,x);
    		u=fa[top[u]];
    	}
    	if(id[u]>id[v]) swap(u,v);
    	L=id[u];
    	R=id[v];
    	add(1,1,N,x);
    }
    
    void solve2(int u,int v){
    	int ans=0;
    	while(top[u]!=top[v]){
    		if(dep[top[u]]<dep[top[v]]) swap(u,v);
    		L=id[top[u]];
    		R=id[u];
    		ans=(ans+Query(1,1,N))%P;
    		u=fa[top[u]];
    	}
    	if(id[u]>id[v]) swap(u,v);
    	L=id[u];
    	R=id[v];
    	ans=(ans+Query(1,1,N))%P;
    	printf("%d
    ",ans);
    }
    
    void solve3(int u,int v){
    	L=id[u];
    	R=id[u]+siz[u]-1;
    	add(1,1,N,v);
    }
    
    void solve4(int u){
    	L=id[u];
    	R=id[u]+siz[u]-1;
    	printf("%d
    ",Query(1,1,N));
    }
    
    int main()
    {
    	fill(head,head+maxn,-1);
    	N=read();
    	M=read();
    	rt=read();
    	P=read();
    	int a,b,cmd;
    	for(int i=1;i<=N;i++) A[i]=read();
    	for(int i=1;i<N;i++){
    		a=read();
    		b=read();
    		build(a,b);
    	}
    	dfs1(rt,0,0);
    	dfs2(rt,0,0);
    	//for(int i=1;i<=N;i++) printf("%d ",Hash[i]);cout<<endl;
    	build(1,1,N);
    	while(M--){
    		cmd=read();
    		a=read();
    		switch(cmd){
    			case 1:b=read();solve1(a,b,read());break;
    			case 2:b=read();solve2(a,b);break;
    			case 3:b=read();solve3(a,b);break;
    			case 4:solve4(a);break;
    			default:break;
    		}
    	}
    	return 0;
    }
    

  • 相关阅读:
    在Linux上安装Memcached服务(转)
    linux下启动和停止memcached(转)
    Windows/Linux/Mac下myeclipse所有版本下载地址
    ubuntu安装配置jdk
    ubuntu安装配置tomcat
    上传文件出错:org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Stream ended unexpectedly
    httpclient上传文件乱码
    Struts2文件上传
    struts2解决上传文件问题
    vue 去中心化的路由拆分方案:require.context
  • 原文地址:https://www.cnblogs.com/Mychael/p/8282897.html
Copyright © 2011-2022 走看看