#4258. 铃铛计数问题

题意

内存限制:256 MiB
时间限制:2000 ms
圣诞节来了,仓鼠又要来策划活动了,今年仓鼠会在圣诞树上挂上铃铛!
已知圣诞树有 n n n 个节点,并且根节点是固定的。记 s [ i ] s[i] s[i] 表示以 i i i 为根的子树中,所有节点上铃铛数目的总和。但仓鼠觉得询问 s [ i ] s[i] s[i] 太简单了,他决定给定 l l l r r r,要你回答 ∑ i = l r s [ i ] \sum\limits_{i=l}^{r}s[i] i=lrs[i] 的值。
但是为了避免有的人一次预处理后一劳永逸,仓鼠在大家答题的过程中还会修改某个节点上灯笼的数量。仓鼠还要去筹备活动,你能帮助他写一个程序帮助实时给出标准答案吗?
n , q ≤ 100000 n,q \leq 100000 n,q100000

题解

①考虑对原编号分块,
f i , j f_{i,j} fi,j 表示 i i i 点对 j j j 块所造成的贡献, s j s_j sj 表示 j j j 这一块的答案
1)考虑修改
假设在 x x x 点上加上 v v v ,那就利用 f f f 数组进行对每个 s j s_j sj 进行修改
2)考虑查询
设查询 [ l , r ] [l,r] [l,r] ,设 b l b_l bl l l l 在的块
那对于 [ b l + 1 , b r − 1 ] [b_l+1,b_r-1] [bl+1,br1] 的块可以累加 s s s
对于剩下的,我们需要每个点 O ( 1 ) O(1) O(1) 求出答案
②对 d f s dfs dfs 序进行分块
s u m i sum_i sumi 表示 d f s dfs dfs 序不小于 i i i 的点的权值和
t a g j tag_j tagj 表示这个块的后缀和都要加上的值
对于修改,我们只需要暴力修改 x x x 的块的每个 s u m x sum_x sumx ,然后再把 [ 1 , b x − 1 ] [1,b_x-1] [1,bx1] 的块的 t a g tag tag 加上 v v v
回到原来的问题,每个点对应着一段 d f s dfs dfs [ l , r ] [l,r] [l,r] ,所以它的答案就是 s u m l + t a g b l − s u m r + 1 − t a g b r + 1 sum_l+tag_{b_l}-sum_{r+1}-tag_{b_{r+1}} suml+tagblsumr+1tagbr+1
效率 O ( n × s q r t ( n ) ) O(n \times sqrt(n)) O(n×sqrt(n))

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100005,M=350;
int w[N],n,q,rt,hd[N],V[N*2],nx[N*2],c[M];
int f[N][M],t,tt,B,b[N],Z,in[N],ot[N];
LL sum[N],tg[M],s[M],A,sz[N];
void add(int u,int v){
	V[++t]=v;nx[t]=hd[u];hd[u]=t;
}
void dfs(int x,int fa){
	sum[in[x]=++tt]=sz[x]=w[x];f[x][b[x]]++;
	for (int i=hd[x];i;i=nx[i])
		if (V[i]!=fa){
			for (int j=1;j<=Z;j++)
				f[V[i]][j]=f[x][j];
			dfs(V[i],x),sz[x]+=sz[V[i]];
		}
	ot[x]=tt;s[b[x]]+=sz[x];
}
int main(){
	scanf("%d%d",&n,&q);B=sqrt(n);
	for (int i=1;i<=n;i++){
		scanf("%d",&w[i]),b[i]=(i-1)/B+1;
		if (b[i]!=b[i-1]) c[b[i-1]]=i-1;
	}
	for (int x,y,i=1;i<=n;i++){
		scanf("%d%d",&x,&y);
		if (!x) rt=y;else add(x,y),add(y,x);
	}
	c[Z=b[n]]=n;dfs(rt,0);
	for (int i=n;i;i--) sum[i]+=sum[i+1];
	for (int op,l,r,j;q--;){
		scanf("%d%d%d",&op,&l,&r);
		if (op&1){
			A=r-w[l];w[l]=r;for (j=1;j<=Z;j++)
				s[j]+=A*f[l][j];l=in[l];
			for (j=l;b[j]==b[l];j--) sum[j]+=A;
			for (j=b[j];j;j--) tg[j]+=A;continue;
		}
		A=0;for (j=b[l]+1;j<b[r];j++) A+=s[j];
		for (j=l;j<=c[b[l]] && j<=r;j++)
			A+=sum[in[j]]+tg[b[in[j]]]-sum[ot[j]+1]-tg[b[ot[j]+1]];
		if (b[l]<b[r])
			for (j=c[b[r]-1]+1;j<=r;j++)
				A+=sum[in[j]]+tg[b[in[j]]]-sum[ot[j]+1]-tg[b[ot[j]+1]];
		printf("%lld\n",A);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值