[BZOJ4765]普通计算姬

Posted by Dispwnl on February 13, 2019

题面

Description

“奋战三星期,造台计算机”。小G响应号召,花了三小时造了台普通计算姬。普通计算姬比普通计算机要厉害一些。普通计算机能计算数列区间和,而普通计算姬能计算树中子树和。更具体地,小G的计算姬可以解决这么个问题:给定一棵n个节点的带权树,节点编号为1到n,以root为根,设sum[p]表示以点p为根的这棵子树中所有节点的权值和。计算姬支持下列两种操作:

1 给定两个整数u,v,修改点u的权值为v。

2 给定两个整数l,r,计算sum[l]+sum[l+1]+….+sum[r-1]+sum[r]

尽管计算姬可以很快完成这个问题,可是小G并不知道它的答案是否正确,你能帮助他吗?

Input

第一行两个整数n,m,表示树的节点数与操作次数。

接下来一行n个整数,第i个整数di表示点i的初始权值。

接下来n行每行两个整数ai,bi,表示一条树上的边,若ai=0则说明bi是根。

接下来m行每行三个整数,第一个整数op表示操作类型。

若op=1则接下来两个整数u,v表示将点u的权值修改为v。

若op=2则接下来两个整数l,r表示询问。

N<=10^5,M<=10^5

0<=Di,V<2^31,1<=L<=R<=N,1<=U<=N

Output

对每个操作类型2输出一行一个整数表示答案。

Sample Input

6 4
0 0 3 4 0 1
0 1
1 2
2 3
2 4
3 5
5 6
2 1 2
1 1 1
2 3 6
2 3 5

Sample Output

16
10
9

题解

子树和立马想到在$dfs$序上操作了,但是要求的答案是原来序列的一段区间……

考虑分块,零散的部分可以通过统计$dfs$序上区间和搞定

正块部分处理出来每个点对当前块的贡献次数,这个$dfs$一遍就能$O(n\sqrt n)$搞出来

具体做法就是记录每个块标号出现次数,$dfs$到$x$点时块$i$的标号出现了$k$次,说明$x$对块$i$贡献次数就是$k$了

为什么跑的这么慢呢

代码

# include<iostream>
# include<cstring>
# include<cstdio>
# include<cmath>
# include<algorithm>
# define LL unsigned long long
using namespace std;
const int MAX=1e5+5,N=318;
struct p{
	int x,y;
}c[MAX<<1];
int n,m,num,cnt,RT,k,_N;
int h[MAX],id[MAX],a[MAX],pos[MAX],bl[N],br[N],siz[MAX],tim[N];
int f[N][MAX];
LL s[MAX],Sum[N];
void add(int x,int y)
{
	c[++num]=(p){h[x],y},h[x]=num;
	c[++num]=(p){h[y],x},h[y]=num;
}
void dfs(int x=RT,int fa=0)
{
	id[x]=++cnt,siz[x]=1,++tim[pos[x]];
	for(int i=1;i<=_N;++i)
	  f[i][x]+=tim[i];
	for(int i=h[x];i;i=c[i].x)
	  if(c[i].y^fa) dfs(c[i].y,x),siz[x]+=siz[c[i].y];
	--tim[pos[x]];
}
int lowbit(int x) {return x&-x;}
void change(int x,int dis)
{
	for(int i=x;i<=n;i+=lowbit(i))
	  s[i]+=dis;
}
LL _sum(int x)
{
	LL ans=0;
	for(int i=x;i;i-=lowbit(i))
	  ans+=s[i];
	return ans;
}
LL ask(int l,int r) {return _sum(r)-_sum(l-1);}
int read()
{
	int x(0);
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar());
	for(;isdigit(ch);x=x*10+ch-48,ch=getchar());
	return x;
}
LL Ask(int l,int r)
{
	LL ans=0;
	int Pl=pos[l],Pr=pos[r];
	if(Pl==Pr)
	{
		if(bl[Pl]==l&&br[Pl]==r) return Sum[Pl];
		for(int i=l;i<=r;++i)
		  ans+=ask(id[i],id[i]+siz[i]-1);
		return ans;
	}
	int L=Pl+(bl[Pl]!=l),R=Pr-(br[Pr]!=r);
	for(int i=L;i<=R;++i)
	  ans+=Sum[i];
	if(L!=Pl) for(int i=l;i<=br[Pl];++i)
	  ans+=ask(id[i],id[i]+siz[i]-1);
	if(R!=Pr) for(int i=bl[Pr];i<=r;++i)
	  ans+=ask(id[i],id[i]+siz[i]-1);
	return ans;
}
int main()
{
	k=sqrt(n=read()),m=read(),k+=(!!n%k);
	for(int i=1,x;i<=n;++i)
	  {
	  	a[i]=read(),pos[i]=x=(i-1)/k+1,br[x]=i;
	  	if(pos[i]!=pos[i-1]) ++_N;
		if(!bl[x]) bl[x]=i;
	  }
	for(int i=1,x;i<=n;++i)
	  {
	  	x=read();
	  	if(!x) RT=read();
	  	else add(x,read());
	  }
	dfs();
	for(int i=1;i<=n;++i)
	  {
	  	change(id[i],a[i]);
	  	for(int j=1;j<=_N;++j)
	  	  Sum[j]+=1ll*f[j][i]*a[i];
	  }
	for(int i=1,op,l,r;i<=m;++i)
	  {
	  	op=read(),l=read(),r=read();
	  	if(op==1)
	  	{
	  		change(id[l],r-a[l]);
			for(int j=1;j<=_N;++j)
			  Sum[j]+=1ll*f[j][l]*(r-a[l]);
			a[l]=r;
		}
	  	else if(op==2) printf("%llu\n",Ask(l,r));
	  }
	return 0;
}