树链剖分,树剖

树剖是把一棵树拆成一堆链,\(O(logn)\)地跳链,用一些数据结构维护每条链,从而实现增加1k代码而降低复杂度到\(O(log^2n)\)的效果。

树链剖分大概分三种:长链剖分,实链剖分和重链剖分。一般说树剖就是重链剖分。

如何把树拆成链?我们定义一个节点的重子节点是它子树最大的儿子,其余的为轻子节点。于是我们把一个节点与它的所有重儿子连成链。

重子节点所组成的链叫重链。显然,一条链的起点一定是轻子节点。于是我们成功把树剖了。

接下来是如何维护这些链的问题。我们树上套数据结构常用dfs序。这个也是,只要我们求dfs序的时候从重子节点开始搜索,就可以保证重链上的dfs序是连续的。

//找重儿子  
void dfs1(int x,int f,int d){
	fa[x]=f;dep[x]=d;size[x]=1;son[x]=0;//我们需要存每个节点的重儿子和父亲 
	int maxsize=0;
	for(int i=head[x];i;i=edge[i].next){
		if(!dep[edge[i].v]){
			dfs1(edge[i].v,x,d+1);
			size[x]+=size[edge[i].v];
			if(maxsize<size[edge[i].v]){
				maxsize=size[edge[i].v];
				son[x]=edge[i].v;//dfs找重儿子 
			}
		}
	}
}
//连重链  
void dfs2(int x,int f){
	dfn[x]=++num;top[x]=f;rnk[num]=x;
	wei[num]=val[x];
	if(son[x])dfs2(son[x],f);//每次搜索优先从重儿子开始 保证了重链上的dfs序连续 
	for(int i=head[x];i;i=edge[i].next){
		if(edge[i].v!=son[x]&&edge[i].v!=fa[x])dfs2(edge[i].v,edge[i].v);
	}
}
dfs1(1,0,1);dfs2(1,0);build(1,1,n);

然后是树上的修改与查询。(其实差不太多)

关于修改,我们每次大概可以找到树上要修改的链上的lca。于是我们每次修改该点所在的一条链然后往上面一条链上跳,一直跳到同一条链。具体见注释。

void change(int x,int y,int val){
	while(top[x]!=top[y]){//不在同一条链上 
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		update(1,dfn[top[x]],dfn[x],val);//修改当前链 
		x=fa[top[x]];//往上跳 
	}
	if(dep[x]>dep[y])swap(x,y);
	update(1,dfn[x],dfn[y],val);//在同一条链上 直接修改 
}
int querysum(int x,int y){
	int num=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]])swap(x,y);
		num+=query(1,dfn[top[x]],dfn[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	num+=query(1,dfn[x],dfn[y]);
	return num;
}

然后对子树整体的操作,我们之前dfs序的时候整棵子树的dfs序都连续,所以直接改就行。

query(1,dfn[x],dfn[x]+size[x]-1)
posted @ 2022-09-03 11:40  gtm1514  阅读(42)  评论(0编辑  收藏  举报