bzoj 4765: 普通计算姬(分块+树状数组)

4765: 普通计算姬

Time Limit: 30 Sec   Memory Limit: 256 MB
Submit: 1481   Solved: 318
[ Submit][ Status][ Discuss]

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


令sum[x]表示以x为根的子树权值和

而求出每个节点的dfs序之后,属于同一棵子树的所有节点一定是连续的,这样sum[x]就可以用树状数组logn查询

考虑每次询问l, r暴力sum[l]到sum[r]:

预处理复杂度:O(nlogn),每次查询:O(nlogn),修改O(logn)


修改复杂度挺低的,但查询复杂度过高,考虑将sum[]分块

首先每修改一个点都可能对多个sum[]造成影响,所以要先nsqrt(n)预处理每个节点修改之后对每一块的影响

然后对于每次询问l, r,暴力每一块,如果当前块完全在区间[l, r]内直接加上块的权值,如果不是就树状数组暴力求sum[]然后再加在一起

修改的话就是看当前点对每一块的权值贡献,然后单点更新树状数组

预处理复杂度:O(nsqrt(n)),每次查询:O(nlognsqrt(n)),修改O(sqrt(n))


其实还有更快的方法,就是对dfs序数列也分块,但常数过大并没有快多少

大概是8000ms优化到5500ms

#include<stdio.h>
#include<vector>
#include<math.h>
#include<algorithm>
using namespace std;
#define LL unsigned long long
vector<int> G[100005];
int n, cnt, dfn[100005], End[100005];
int B, val[100005], bel[100005], L[100005], R[100005], giv[100005][322];
LL tre[100005], sb[322], temp[322];
int Read()
{
	int x = 0, f = 1;
	char ch;
	ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-')  f = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
		x = x*10+ch-'0', ch = getchar();
	return x*f;
}
void Update(int k, int sv)
{
	while(k<=n)
	{
		tre[k] += sv;
		k += k&-k;
	}
}
LL Query(int k)
{
	LL now = 0;
	while(k)
	{
		now += tre[k];
		k -= k&-k;
	}
	return now;
}
void Sech(int u, int p)
{
	int i, v;
	dfn[u] = ++cnt;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		Sech(v, u);
	}
	End[u] = cnt;
}
void Sech2(int u, int p)
{
	int i, v;
	temp[bel[u]]++;
	for(i=1;i<=B;i++)
		giv[u][i] = temp[i];
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		Sech2(v, u);
	}
	temp[bel[u]]--;
}
int main(void)
{
	LL ans;
	//freopen("in.txt", "r", stdin);
	//freopen("out2.txt", "w", stdout);
	int q, root, i, x, y, opt, block, j, ly;
	scanf("%d%d", &n, &q);
	for(i=1;i<=n;i++)
		val[i] = Read();
	for(i=1;i<=n;i++)
	{
		x = Read(), y = Read();
		if(x==0)
			root = y;
		else
		{
			G[x].push_back(y);
			G[y].push_back(x);
		}
	}
	Sech(root, 0);
	for(i=1;i<=n;i++)
		Update(dfn[i], val[i]);
	y = 1, x = 0;
	block = sqrt(n)+1;
	for(i=1;i<=n;i++)
	{
		x++;
		if(x==1)  L[y] = i;
		if(x==block)  R[y] = i;
		bel[i] = y;
		sb[y] += Query(End[i])-Query(dfn[i]-1);
		if(x==block)
			x = 0, y++;
	}
	if(x==0)
		y--;
	else
		R[y] = i-1;
	B = y;
	Sech2(root, 0);
	while(q--)
	{
		opt = Read(), x = Read(), y = Read();
		if(opt==1)
		{
			for(i=1;i<=B;i++)
				sb[i] += (LL)(y-val[x])*giv[x][i];
			Update(dfn[x], y-val[x]);
			val[x] = y;
		}
		else
		{
			ans = 0;
            for(i=1;i<=B;i++)
            {
                if(L[i]>=x && R[i]<=y)
                    ans += sb[i];
                else if(x>L[i] && x<=R[i])
                {
                    ly = min(R[i], y);
                    for(j=x;j<=ly;j++)
                        ans += Query(End[j])-Query(dfn[j]-1);
                }
                else if(y>=L[i] && y<R[i])
                {                   
                    for(j=L[i];j<=y;j++)
                        ans += Query(End[j])-Query(dfn[j]-1);
					break;
                }
            }
			printf("%llu\n", ans);
		}
	}
}
/*
1 3
2
0 1
2 1 1
1 1 5
2 1 1
*/


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值