多校联测12 树

文章描述了一种解决关于具有n个节点的树结构问题的方法,涉及q次操作,包括区间修改和查询。使用根号分治策略和dfs序列进行高效处理,通过分块技巧优化时间复杂度为O(q√n),空间复杂度为O(nn√n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

有一棵有 n n n个点且以 1 1 1为根的树,有 q q q次操作,操作分为两种类型:

  • 将点 v v v子树内与它距离模 x x x等于 y y y的所有点的权值加上 z z z
  • 询问点 v v v的权值

1 ≤ n , q ≤ 3 × 1 0 5 , 1 ≤ z ≤ 1000 1\leq n,q\leq 3\times 10^5,1\leq z\leq 1000 1n,q3×105,1z1000

时间限制 6000 m s 6000ms 6000ms,空间限制 256 M B 256MB 256MB


题解

前置知识:根号分治

设点 i i i在树上的深度为 d e p i dep_i depi,则第一类操作可以看成将 v v v的子树内所有满足 d e p i % x = ( d e p v + y ) % p dep_i\%x=(dep_v+y)\%p depi%x=(depv+y)%p的点 i i i的权值加上 z z z

我们可以求每个点在树上的 d f s dfs dfs序,那么在一个子树内的节点在 d f s dfs dfs序上是一段连续的区间,这样操作一就变成区间修改了。

我们可以用分块来维护 d f s dfs dfs序上每个点的权值。

我们考虑根据 x x x的大小来进行根号分治。在操作二时,将两个部分的答案加在一起即可得到答案。

x ≤ n x\leq \sqrt n xn 时,设 m d i , j , k md_{i,j,k} mdi,j,k为第 i i i块中深度模 j j j k k k的点需要整体加的值,对于操作一(也就是一次区间修改),分为整块和散块处理,是 O ( n ) O(\sqrt n) O(n )的。而操作二要对每一个小于等于 n \sqrt n n 的模数 j j j都要 O ( 1 ) O(1) O(1)查询,所以也是 O ( n ) O(\sqrt n) O(n )的。那么,这部分的时间复杂度为 O ( q n ) O(q\sqrt n) O(qn )

x > n x>\sqrt n x>n 时,设 d s i , j ds_{i,j} dsi,j表示深度为 i i i时第 j j j块上要加的值。也是分为整块和散块来处理。对于整块,枚举每个满足题意的深度。如果将每个深度的每一块都修改的话,一次操作一是 O ( n ) O(n) O(n)的,所以我们可以维护这些块的拆分,那么对每一种深度都可以 O ( 1 ) O(1) O(1)修改,是 O ( n ) O(\sqrt n) O(n )的。对于操作二,在 v v v的深度对应的块中求对应段的 d s i , j ds_{i,j} dsi,j之和即可,直接枚举并求和,也是 O ( n ) O(\sqrt n) O(n )的。那么,这部分的时间复杂度为 O ( q n ) O(q\sqrt n) O(qn )

总时间复杂度为 O ( q n ) O(q\sqrt n) O(qn ),空间复杂度为 O ( n n ) O(n\sqrt n) O(nn ),这是按块数和块长为 n \sqrt n n 来得到的。如果 M L E MLE MLE,可以适当改变块数和块长。

code

#include<bits/stdc++.h>
using namespace std;
const int N=300000,B=200,K=1500;
int n,Q,bl,ct=0,tot=0,d[2*N+5],l[2*N+5],r[2*N+5],dfn[N+5],s[N+5],dep[N+5],siz[N+5];
int mx,ans,hv[N+5],ds[N+5][B+5],md[K+5][B+5][B+5];
void add(int xx,int yy){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
int pos(int i){
	return (i-1)/bl+1;
}
void dfs(int u,int fa){
	dfn[u]=++ct;
	s[ct]=u;
	siz[u]=1;
	dep[u]=dep[fa]+1;
	mx=max(mx,dep[u]);
	for(int i=r[u];i;i=l[i]){
		if(d[i]==fa) continue;
		dfs(d[i],u);
		siz[u]+=siz[d[i]];
	}
}
void pt(int dd,int l,int r,int x,int y,int z){
	int vl=pos(l),vr=pos(r);
	if(vl==vr){
		for(int i=l;i<=r;i++){
			if(dep[s[i]]%x==y) hv[i]+=z;
		}
		return;
	}
	for(int i=l;i<=vl*bl;i++){
		if(dep[s[i]]%x==y) hv[i]+=z;
	}
	for(int i=vr*bl-bl+1;i<=r;i++){
		if(dep[s[i]]%x==y) hv[i]+=z;
	}
	if(x<=B){
		for(int i=vl+1;i<=vr-1;i++) md[i][x][y]+=z;
	}
	else{
		for(int i=dd;i<=mx;i+=x){
			ds[i][vl+1]+=z;ds[i][vr]-=z;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&Q);
	bl=n/B+1;
	for(int i=1,x,y;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs(1,0);
	for(int i=1;i<=Q;i++){
		int tp,v,x,y,z;
		scanf("%d",&tp);
		if(tp==1){
			scanf("%d%d%d%d",&v,&x,&y,&z);
			pt(dep[v]+y,dfn[v],dfn[v]+siz[v]-1,x,(y+dep[v])%x,z);
		}
		else{
			scanf("%d",&v);
			ans=hv[dfn[v]];
			for(int i=1;i<=B;i++){
				ans+=md[pos(dfn[v])][i][dep[v]%i];
			}
			for(int i=1;i<=pos(dfn[v]);i++){
				ans+=ds[dep[v]][i];
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值