【总结】重链剖分

好吧,现在让我来说一下树链剖分中的重链剖分。

Part one

首先让我来说一下什么是树链剖分。

实如其名,就是将一棵树化分成若干条连,下面是一个图。

img

在这个图中你怎末划分都行。

下面是几种方案。

\(1\) 条链:\(1,2,4\)

\(2\) 条链:\(5,9\)

\(3\) 条连:\(6\)

\(4\) 条链:\(3,8\)

\(5\) 条链:\(7,10,11\)

这就是一种划分的方式。

不过把数划分成若干条连有什莫好处呢。(没好处你学它干甚。

这可以方便你处理一些树上问题。(废话,不然还能处理序列上的问题吗。

Part two

你可能会问,怎末就 \(part2\) 了呢,上头都讲了写啥?

实际就是基本上啥也没讲。

别急,现在开始本文的重头戏。

树链剖分可以分成两类,一类叫作长链剖分,一类叫作重链剖分。

而长链剖分就是让每个点向它子孙中最深的点剖。

就像下图:

img

这个的用处主要在于 dp 优化啥的,今天先不讲。

下面是今天的主角——重链剖分。

重链剖分又是啥呢,就是每一个节点向它的儿子当中子孙最多的节点剖,就像这样:

img

而重链剖分支干啥的呢。

它可以对树上进行区间操作,比如树上子树加,树上路经查询啥的。

虽然这里用区间很不准确,但一想到这个词,想必很多人会想到另一个名词:线段树。

线段树是干啥的,是用来维护序列上的区间操作。

所以就会有人想,能不能在树上建线段树。

于是重链剖分出现了。

那这和重链剖分有啥关系呢?

想一下,树为什么不能建线段树,因为树的各个节点很难组成一段连续的区间。

但是如果找到一个顺序,将树分成好几块,操作和查询的时候也是分成好几块,这样就会大大降低复杂度。

考虑 dfs 序,这个是比较容易被卡的,因为太不连贯了,所以排除。

接下来让我们谈谈按照重链头的 dfs 顺序排序的首尾相连的序列。

先想一下如何操作以 \(x\) 为根的子树。

首先一个树的根会先被 dfs 到,且在 dfs 完这颗子树前是不会到其他子树内的,所以到这颗子树的最后一个点只需加上这个子树大小再减 \(1\) 即可,这就是这棵子树映射到序列上的区间。

同时这也是 dfs 序所拥有的。

查询 \(x\)\(y\) 的路径呢。

首先一个点要么在链中要么在链首(纯废话。

那么我们可以重链长驱直上,直至 \(x\)\(y\) 到了同一个点即可。

img

这是 \(9,13,11,8\)\(4\) 个叶子节点到 \(1\) 节点的路径。

可见这是十分连贯的,所以说重链是可以实现树上路径修改的。

Part three

现在让我们对着例题详将一下代码。

Step one

先用一个 dfs 去处理一下一个结点的重儿子,父节点,子树大小,深度。

ll fa[N],siz[N],dep[N]/*deep(深度)*/;
ll hson[N];
void dfs1(ll x,ll fax){
	fa[x]=fax;
	siz[x]=1;
	dep[x]=dep[fax]+1;
	ll g=-1;
	for(ll i=0;i<e[x].size();i++){
		ll v=e[x][i];
		if(v==fax)continue;
		dfs1(v,x);
		siz[x]+=siz[v];
		if(g<siz[v]){
			g=siz[v];
			hson[x]=v;
		}
	}
} 

Step two

再用一个 dfs 去处理节点所属链头在映射序列中的位置,和该节点在序列上的位置。

注意这里应该先 dfs 重儿子,为啥,因为这是重链,重链上的点应该是连续的(虽然这样解释没有一点逻辑性,但至少对我来说是能让这个更好记)。

ll top[N];
ll tot=0;
ll dfn[N];
void dfs2(ll x,ll zt/*重(z)链top*/){
	top[x]=zt;
	dfn[x]=++tot;
	if(w[x]!=0)modify(1,1,n,dfn[x],dfn[x],w[x]); 
	if(hson[x]==0)return ;
	dfs2(hson[x],zt);
	for(ll i=0;i<e[x].size();i++){
		ll v=e[x][i];
		if(v==fa[x]||v==hson[x])continue;
		dfs2(v,v);
	}
}

Step three

查询和修改。

对子树的部分已经说过了。

void add_sontree(ll x,ll z){
	z%=mod;
	modify(1,1,n,dfn[x],dfn[x]+siz[x]-1,z);
}
ll query_sontree(ll x){
	return query(1,1,n,dfn[x],dfn[x]+siz[x]-1);	
}

\(x\)\(y\) 路径部分。

就是像找 \(lca\) 一样向上跳,只不过 \(lca\) 是倍增,这里是跳重链。

什么时候不跳了呢,就是到他两个跳到了同一个重链的时候就不用跳了。

那在同一个重链上了点的编号就是连续的了,就直接搞就行了啊。

void add_path(ll l,ll r,ll k){
	k%=mod;
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		modify(1,1,n,dfn[top[l]],dfn[l],k);
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	modify(1,1,n,dfn[l],dfn[r],k);
}
ll query_path(ll l,ll r){
	ll cnt=0;
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		cnt=(cnt+query(1,1,n,dfn[top[l]],dfn[l]))%mod;
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	cnt=(cnt+query(1,1,n,dfn[l],dfn[r]))%mod;
	return cnt;	
}

Step four

#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const ll N=1e5+5;
ll n,q,root,mod;
struct node{
	ll sum;
	ll lazy_tag; 
	ll l,r;
}tr[N*4];
void build(ll id,ll l,ll r){
	tr[id].l=l;
	tr[id].r=r;
	tr[id].lazy_tag=0;
	if(l==r){
		tr[id].sum=0;
		return ;
	}
	ll mid=(l+r)/2;
	build(id*2,l,mid);
	build(id*2+1,mid+1,r);
	tr[id].sum=tr[id*2].sum+tr[id*2+1].sum;
}
void pushdown(ll id){
	if(tr[id].lazy_tag!=0){
//		tr[id].lazy_tag%=mod;
		tr[id*2].lazy_tag+=tr[id].lazy_tag;
		tr[id*2].lazy_tag%=mod;
		tr[id*2+1].lazy_tag+=tr[id].lazy_tag;
		tr[id*2+1].lazy_tag%=mod;
		tr[id*2].sum=(tr[id*2].sum+((tr[id*2].r-tr[id*2].l+1)*tr[id].lazy_tag)%mod)%mod;
		tr[id*2+1].sum=(tr[id*2+1].sum+((tr[id*2+1].r-tr[id*2+1].l+1)*tr[id].lazy_tag)%mod)%mod;
		tr[id].lazy_tag=0;
	}
}
void modify(ll id,ll l,ll r,ll ql,ll qr,ll k){
	if(ql<=l&&r<=qr){
		tr[id].lazy_tag=(tr[id].lazy_tag+k)%mod;
		tr[id].sum=(tr[id].sum+(k*(r-l+1))%mod)%mod;
		return ;
	}
	pushdown(id);
	ll mid=(l+r)/2;
	if(ql<=mid)modify(id*2,l,mid,ql,qr,k);
	if(qr>mid)modify(id*2+1,mid+1,r,ql,qr,k);
	tr[id].sum=(tr[id*2].sum+tr[id*2+1].sum)%mod;
}
ll query(ll id,ll l,ll r,ll ql,ll qr){
	if(ql<=l&&r<=qr)return tr[id].sum;
	pushdown(id);
	ll mid=(l+r)/2;
	ll ans=0; 
	if(ql<=mid)ans=(ans+query(id*2,l,mid,ql,qr))%mod;
	if(qr>mid)ans=(ans+query(id*2+1,mid+1,r,ql,qr))%mod;
	return ans;
}
ll w[N];
vector<ll>e[N];
ll fa[N],siz[N],dep[N]/*deep(深度)*/;
ll hson[N];
void dfs1(ll x,ll fax){
	fa[x]=fax;
	siz[x]=1;
	dep[x]=dep[fax]+1;
	ll g=-1;
	for(ll i=0;i<e[x].size();i++){
		ll v=e[x][i];
		if(v==fax)continue;
		dfs1(v,x);
		siz[x]+=siz[v];
		if(g<siz[v]){
			g=siz[v];
			hson[x]=v;
		}
	}
} 
ll top[N];
ll tot=0;
ll dfn[N];
void dfs2(ll x,ll zt/*重(z)链top*/){
	top[x]=zt;
	dfn[x]=++tot;
	if(w[x]!=0)modify(1,1,n,dfn[x],dfn[x],w[x]); 
	if(hson[x]==0)return ;
	dfs2(hson[x],zt);
	for(ll i=0;i<e[x].size();i++){
		ll v=e[x][i];
		if(v==fa[x]||v==hson[x])continue;
		dfs2(v,v);
	}
}
void add_path(ll l,ll r,ll k){
	k%=mod;
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		modify(1,1,n,dfn[top[l]],dfn[l],k);
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	modify(1,1,n,dfn[l],dfn[r],k);
}
ll query_path(ll l,ll r){
	ll cnt=0;
	while(top[l]!=top[r]){
		if(dep[top[l]]<dep[top[r]])swap(l,r);
		cnt=(cnt+query(1,1,n,dfn[top[l]],dfn[l]))%mod;
		l=fa[top[l]];
	}
	if(dep[l]>dep[r])swap(l,r);
	cnt=(cnt+query(1,1,n,dfn[l],dfn[r]))%mod;
	return cnt;	
}
void add_sontree(ll x,ll z){
	z%=mod;
	modify(1,1,n,dfn[x],dfn[x]+siz[x]-1,z);
}
ll query_sontree(ll x){
	return query(1,1,n,dfn[x],dfn[x]+siz[x]-1);	
}
int main(){
	cin>>n>>q>>root>>mod;
	for(ll i=1;i<=n;i++)cin>>w[i];
	for(ll i=1;i<n;i++){
		ll u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	build(1,1,n);
	dfs1(root,0);
	dfs2(root,root);
	while(q--){
		ll op;
		cin>>op;
		if(op==1){
			ll l,r,k;
			cin>>l>>r>>k;
			add_path(l,r,k);
		}else if(op==2){
			ll l,r;
			cin>>l>>r;
			cout<<query_path(l,r)<<"\n";
		}else if(op==3){
			ll x,z;
			cin>>x>>z;
			add_sontree(x,z);
		}else{
			ll x;
			cin>>x;
			cout<<query_sontree(x)<<"\n";
		}
	}
	return 0;
}

打线段树一定要建树,不然。。。

img

posted @ 2025-08-10 19:45  Kcjhfqr  阅读(10)  评论(0)    收藏  举报
.poem-wrap { position: relative; width: 1000px; max-width: 80%; border: 2px solid #797979; border-top: none; text-align: center; margin: 40px auto; } .poem-left { left: 0; } .poem-right { right: 0; } .poem-border { position: absolute; height: 2px; width: 27%; background-color: #797979; } .poem-wrap p { width: 70%; margin: auto; line-height: 30px; color: #797979; } .poem-wrap h1 { position: relative; margin-top: -20px; display: inline-block; letter-spacing: 4px; color: #797979; font-size: 2em; margin-bottom: 20px; } #poem_sentence { font-size: 25px; } #poem_info { font-size: 15px; margin: 15px auto; }