洛谷 P3384 【模板】轻重链剖分/树链剖分

PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接:洛谷 P3384 【模板】轻重链剖分/树链剖分

题目

题目描述

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

  • 1 x y z,表示将树从 x x x y y y 结点最短路径上所有节点的值都加上 z z z

  • 2 x y,表示求树从 x x x y y y 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 x x x 为根节点的子树内所有节点值都加上 z z z

  • 4 x 表示求以 x x x 为根节点的子树内所有节点值之和

输入格式

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

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

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

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

输出格式

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

样例 #1

样例输入 #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

提示

【数据规模】

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

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

对于 100 % 100\% 100% 的数据: 1 ≤ N ≤ 10 5 1\le N \leq {10}^5 1N105 1 ≤ M ≤ 10 5 1\le M \leq {10}^5 1M105 1 ≤ R ≤ N 1\le R\le N 1RN 1 ≤ P ≤ 2 31 − 1 1\le P \le 2^{31}-1 1P2311

【样例说明】

树的结构如下:

各个操作如下:

故输出应依次为 2 2 2 21 21 21

题解

剖分

定义

重儿子:一个节点的子节点中,子树大小最大的子节点。
轻儿子:一个节点的子节点中,除重儿子以外的其他子节点。
重链:一个节点连接到它的重儿子的边。
轻链:一个节点连接到它的轻儿子的边。

数组

d e p dep dep:节点深度(根节点的深度为1)
s o n son son:重儿子的编号
s i z e size size:子树的大小
f a fa fa:父节点
d f n dfn dfn:节点的dfs序
r n k rnk rnk:dfs序对应的节点编号
t o p top top:一条链的链顶

预处理

dfs1

记录 d e p , s o n , s i z e , f a dep,son,size,fa dep,son,size,fa

dfs2

记录 d f n , r n k , t o p dfn,rnk,top dfn,rnk,top
注意要先搜每个节点的重儿子,这样可以保证每条链的 d f n dfn dfn都是连续的。

LCA

每次跳链顶深度较深的节点。

其他操作

基本上都是基于LCA或者子树大小进行的改动,虽然这道题本省不需要求LCA,但为了方便大家理解,我也把求LCA的函数放在代码里面了,大家可以对比查看。

线段树

就是一般的线段树的操作,注意要基于树的dfs序建线段树。

代码实现

100pts

//洛谷 P3384 【模板】轻重链剖分/树链剖分
#pragma GCC optimize(3)
#include<iostream>
#include<cstdio>
using namespace std;
int n,m,r,p;
int x,y,z;
int cnt;
int opt;
int val[100010];
int b[400010];
int head[100010];
int son[100010];
int size[100010];
int dep[100010];
int fa[100010];
int top[100010];
int dfn[100010];
int rnk[100010];
int tag[400010];

struct tree{
	int v,nex;
}a[200010];

void build1(int rt,int l,int r){
	if(l==r){
		b[rt]=val[rnk[l]];
		return;
	}
	int mid=l+r>>1;
	build1(rt<<1,l,mid);
	build1(rt<<1|1,mid+1,r);
	b[rt]=1ll*(b[rt<<1]+b[rt<<1|1])%p;
}

void build2(int u,int v){
	a[++cnt].v=v;
	a[cnt].nex=head[u];
	head[u]=cnt;
}

void dfs1(int u){
	son[u]=-1;
	size[u]=1;
	for(int i=head[u];i;i=a[i].nex){
		int v=a[i].v;
		if(!dep[v]){
			dep[v]=dep[u]+1;
			fa[v]=u;
			dfs1(v);
			size[u]+=size[v];
			if(son[u]==-1||size[v]>size[son[u]]){
				son[u]=v;
			}
		}
	}
}

void dfs2(int u,int t){
	top[u]=t;
	dfn[u]=++cnt;
	rnk[cnt]=u;
	if(son[u]==-1){
		return;
	}
	dfs2(son[u],t);
	for(int i=head[u];i;i=a[i].nex){
		if(a[i].v!=son[u]&&a[i].v!=fa[u]){
			dfs2(a[i].v,a[i].v);
		}
	}
}

void push(int rt,int ll,int rr,int mid){
	tag[rt<<1]=1ll*(tag[rt<<1]+tag[rt])%p;
	tag[rt<<1|1]=1ll*(tag[rt<<1|1]+tag[rt])%p;
	b[rt<<1]=1ll*(b[rt<<1]+1ll*tag[rt]*(mid-ll+1)%p)%p;
	b[rt<<1|1]=1ll*(b[rt<<1|1]+1ll*tag[rt]*(rr-mid)%p)%p;
	tag[rt]=0;
}

void change(int rt,int ll,int rr,int l,int r,int k){
	if(ll>=l&&rr<=r){
		tag[rt]+=k;
		b[rt]+=k*(rr-ll+1);
		return;
	}
	int mid=ll+rr>>1;
	if(tag[rt]){
		push(rt,ll,rr,mid);
	}
	if(l<=mid){
		change(rt<<1,ll,mid,l,r,k);
	}
	if(r>mid){
		change(rt<<1|1,mid+1,rr,l,r,k);
	}
	b[rt]=1ll*(b[rt<<1]+b[rt<<1|1])%p;
}

int query(int rt,int ll,int rr,int l,int r){
	if(ll>=l&&rr<=r){
		return b[rt];
	}
	int mid=ll+rr>>1;
	if(tag[rt]){
		push(rt,ll,rr,mid);
	}
	int ans=0;
	if(l<=mid){
		ans=1ll*(ans+query(rt<<1,ll,mid,l,r))%p;
	}
	if(r>mid){
		ans=1ll*(ans+query(rt<<1|1,mid+1,rr,l,r))%p;
	}
	return ans%p;
}

int lca(int u,int v){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]){
			swap(u,v);
		}
		u=fa[top[u]];
	}
	return dep[u]<dep[v]?u:v;
}

void change1(int x,int y,int z){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]){
			swap(x,y);
		}
		change(1,1,n,dfn[top[x]],dfn[x],z);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]){
		swap(x,y);
	}
	change(1,1,n,dfn[x],dfn[y],z);
}

void change2(int x,int z){
	change(1,1,n,dfn[x],dfn[x]+size[x]-1,z);
}

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

int query2(int x){
	return query(1,1,n,dfn[x],dfn[x]+size[x]-1)%p;
}

int main(){
	register int i;
	scanf("%d%d%d%d",&n,&m,&r,&p);
	for(i=1;i<=n;++i){
		scanf("%d",&val[i]);
	}
	for(i=1;i<n;++i){
		scanf("%d%d",&x,&y);
		build2(x,y);
		build2(y,x);
	}
	dep[r]=1;
	dfs1(r);
	cnt=0;
	dfs2(r,r);
	build1(1,1,n);
	for(i=1;i<=m;++i){
		scanf("%d",&opt);
		if(opt==1){
			scanf("%d%d%d",&x,&y,&z);
			z%=p;
			change1(x,y,z);
		}
		else if(opt==2){
			scanf("%d%d",&x,&y);
			printf("%d\n",query1(x,y));
		}
		else if(opt==3){
			scanf("%d%d",&x,&z);
			z%=p;
			change2(x,z);
		}
		else{
			scanf("%d",&x);
			printf("%d\n",query2(x));
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月半流苏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值