樹剖(不太會)

shaoyufei發表於2024-05-22

前情提要,我主要看的是這位大佬的講解,用的是谷的程式碼,所以會有點奇怪

image
大概就是這麼個意思
image

dfs1用來處理樹的dfs序,處理出重鏈大小和對應的重兒子
image

void dfs1(int now){
	son[now]=-1;
	siz[now]=1; 
	for(int i=head[now];i;i=edge[i].from){
		int to=edge[i].to;
		if(dep[to]) continue;//有長度說明已經算過了,正在往回走(因為建的雙向邊)
		dep[to]=dep[now]+1;
		fa[to]=now;
		dfs1(to);
		siz[now]+=siz[to];//加上所有子樹大小得到本棵樹的大小
		if(son[now]==-1||siz[to]>siz[son[now]]) son[now]=to;//遞迴回來時已經求出了各個子樹的長度,要進行比較,找出重兒子
	}
}

dfs2用來處理重鏈的頭top,以及將樹重新編號dfn,使其符合一條重鏈上編號連續的狀態,這樣我們就可以在樹上實現類似線段樹的區間修改操作
image

void dfs2(int now,int tp){
	top[now]=tp;//tp為重鏈的頭(不屬於重兒子)
	num++;
	b[num]=a[now]; 
//	pre[num]=now; 這個沒必要,可以被上一行的替代 
	dfn[now]=num;//先跑重鏈,確保其連續
	if(son[now]==-1) return;
	dfs2(son[now],tp);
	for(int i=head[now];i;i=edge[i].from){
		int to=edge[i].to;
		if(to!=son[now]&&to!=fa[now]) dfs2(to,to);//重鏈跑完後跑輕鏈
	}
}

看程式碼可知,重鏈上的編號一定是連續的(因為是沿著重兒子跑的),遞迴完後跑輕鏈,找出輕鏈中剩餘的重鏈(如圖中的點3)

然後是路徑求和treesum
image
帶圖就好理解多了,假設我要求3到6路徑上的和,那麼我先將3所處的重鏈和6所處的重鏈的和求出來,利用dfs2的連續標記和線段樹可以快速求和,然後6跳到1上,3跳到2的父親1上,然後3和6就在同一重鏈上,這樣我們直接利用兩點所處重鏈的連續標記再次利用線段樹求和

int treesum(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans+=query(1,dfn[top[x]],dfn[x]);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query(1,dfn[x],dfn[y]);
	return ans;
}

然後沒了,實際上仔細分析還是能分析出來的

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define lson id<<1
#define rson id<<1|1
const int N=1e5+10;
int n,m,r,mod;
int a[N],b[N];
struct node{
	int from;
	int to;
}edge[N<<2];
int head[N],num,cnt;
int fa[N],dep[N],top[N],son[N],dfn[N],idx[N],siz[N];
struct node1{
	int l,r,cnt,lazy,sum;
}tr[N<<2];
void pushup(int id){
	if(!tr[id].lazy) return;
	tr[lson].lazy=(tr[lson].lazy+tr[id].lazy)%mod;
	tr[rson].lazy=(tr[rson].lazy+tr[id].lazy)%mod;
	tr[lson].sum=(tr[lson].sum+tr[id].lazy*tr[lson].cnt)%mod;
	tr[rson].sum=(tr[rson].sum+tr[id].lazy*tr[rson].cnt)%mod;
	tr[id].lazy=0;
}
void build(int id,int l,int r){
	tr[id].l=l;
	tr[id].r=r;
	tr[id].cnt=r-l+1;
	if(l==r){
		tr[id].sum=a[l]%mod;
		return;
	}
	int mid=(l+r)/2;
	build(lson,l,mid);
	build(rson,mid+1,r);
	tr[id].sum=(tr[lson].sum+tr[rson].sum)%mod;
}
void update(int id,int l,int r,int ad){
	if(l>tr[id].r||r<tr[id].l) return;
	if(l<=tr[id].l&&tr[id].r<=r) {
		tr[id].sum=(ad*tr[id].cnt+tr[id].sum)%mod;
		tr[id].lazy=(tr[id].lazy+ad)%mod;
		return;
	}
	pushup(id);
//	int mid=(tr[id].l+tr[id].r)/2;
	update(lson,l,r,ad);
	update(rson,l,r,ad);
	tr[id].sum=(tr[lson].sum+tr[rson].sum)%mod;
}
int getsum(int id,int l,int r){
	if(l>tr[id].r||r<tr[id].l) return 0;
	if(l<=tr[id].l&&tr[id].r<=r) {
		return tr[id].sum%mod;
	}
	pushup(id);
//	int mid=(tr[id].l+tr[id].r)/2;
	return getsum(lson,l,r)+getsum(rson,l,r);
}
void add(int from,int to){
	cnt++;
	edge[cnt].from=head[from];
	edge[cnt].to=to;
	head[from]=cnt;
}
void dfs1(int x){
	son[x]=-1;
	siz[x]=1;
	for(int i=head[x];i;i=edge[i].from){
		int to=edge[i].to;
		if(dep[to]) continue;
		dep[to]=dep[x]+1;
		fa[to]=x;
		dfs1(to);
		siz[x]+=siz[to];
		if(son[x]==-1||siz[to]>siz[son[x]]) son[x]=to;
	}
}
void dfs2(int x,int tp){
	top[x]=tp;
	num++;
	idx[num]=x;
	dfn[x]=num;
	a[num]=b[x];
	if(son[x]==-1) return;
	dfs2(son[x],tp);
	for(int i=head[x];i;i=edge[i].from){
		int to=edge[i].to;
		if(to!=son[x]&&to!=fa[x]) dfs2(to,to);
	}
}
void addtree(int x,int y,int ad){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		update(1,dfn[top[x]],dfn[x],ad);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	update(1,dfn[x],dfn[y],ad);
}
int treesum(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=(ans+getsum(1,dfn[top[x]],dfn[x]))%mod;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans=(ans+getsum(1,dfn[x],dfn[y]))%mod;
	return ans;
} 
int main(){
	int c,from,to,w;
	cin>>n>>m>>r>>mod;
	for(int i=1;i<=n;i++) cin>>b[i];
	for(int i=1;i<n;i++){
		cin>>from>>to;
		add(from,to);
		add(to,from);
	}
	dep[r]=1;
	dfs1(r);
	dfs2(r,r);
	build(1,1,num);
	for(int i=1;i<=m;i++){
		cin>>c;
		if(c==1){
			cin>>from>>to>>w;
			addtree(from,to,w);
		}
		else if(c==2){
			cin>>from>>to;
			cout<<treesum(from,to)%mod<<endl;
		}
		else if(c==3){
			cin>>from>>w;
			update(1,dfn[from],dfn[from]+siz[from]-1,w);
		}
		else{
			cin>>from;
			cout<<getsum(1,dfn[from],dfn[from]+siz[from]-1)%mod<<endl;
		}
	}
}

注意一點,在正常的update下要用對應的dfn值

if(str=="CHANGE"){
	cin>>root>>w;
	update(1,dfn[root],w);
}//對的
if(str=="CHANGE"){
	cin>>root>>w;
	update(1,root,w);
}//錯的

還有一點,由於dfs一條路走到黑的特性,dfs2中樹的編號一定均為dfn[root]到dfn[root]+siz[root]-1(減一是因為左邊界包含了dfn[root],樹的總大小為siz[root]),因此直接update這些編號即可

相關文章