【练习-3】树状数组-模板三(区间修改,区间查询)

直接放题目,然后再解释:
树状数组:
1.单点修改,区间查询
2.区间修改,单点查询
3.区间修改,区间查询

区间修改,区间查询

Description
给定数列 a[1],a[2],…,a[n],你需要依次进行q个操作,操作有两类:

1 l r x:给定l,r,x,对于所有的i∈[l,r],将a[i]加上x(换言之,将a[l],a[l+1],…,a[r] 分别加上x)
2 l r:给定l,r,求∑ri=la[i]的值(换言之,求a[l]+a[l+1]+…+a[r]的值)
Input
第一行包含2个正整数n,q,表示数列长度和询问个数。保证1≤n,q≤106;

第二行n个整数a[1],a[2],…,a[n],表示初始数列。保证|a[i]|≤106。

接下来q行,每行一个操作,为以下两种之一:

1 l r x:对于所有的i∈[l,r],将a[i]加上x;
2  l r:输出∑ri=la[i]的值;
Output
对于每个 2 l r 操作,输出一行,每行有一个整数,表示所求的结果。

Samples
Input
5 10
2 6 6 1 1
2 1 4
1 2 5 10
2 1 3
2 2 3
1 2 2 8
1 2 3 7
1 4 4 10
2 1 2
1 4 5 6
2 3 4
Output
15
34
32
33
50
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106 ,1≤l≤r≤n,|x[i]|≤106 。

AC code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read() {char ch = getchar(); int x = 0, f = 1;while(ch < '0' || ch > '9') {if(ch == '-') f = -1;ch = getchar();} while('0' <= ch && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();} return x * f;}
ll n,q;
ll a,b,v;
ll k,now,last;
ll sum1,sum2;
ll c1[1000005];
ll c2[1000005];
ll lowbit(ll x)	{return x&(-x);}
void add(ll *t,ll x,ll w)
{
	while(x<=n)
	{
		t[x]+=w;
		x += lowbit(x);
	}
	return ;
} 
ll sum(ll *t,ll x)
{
	ll s=0;
	while(x>0)
	{
		s+=t[x];
		x -= lowbit(x);
	}
	return s;
}
int main()
{
	n=read();
	q=read();
	for(ll i=1;i<=n;i++)
	{
		now = read();
		add(c1,i,now-last);
		add(c2,i,(i-1)*(now-last));
		last = now;
	}
	while(q--)
	{
		k =read();
		if(k==1)
		{
			a=read(),b=read(),v=read();	
			add(c1,a,v);add(c1,b+1,-v);
			add(c2,a,v*(a-1));add(c2,b+1,-v*b);
		}
		if(k==2)
		{
			a = read(),b = read();
			sum1=(a-1)*sum(c1,a-1)-sum(c2,a-1);
			sum2=b*sum(c1,b)-sum(c2,b);
			printf("%lld\n",sum2-sum1);	
		}
	}
	return 0;	
}

这一个就比较麻烦了……
这里会发现我们的add函数和sum函数和之前有所不同,多了一个*t是因为我们要使用多个数组才能实现区间查询的操作,具体如下:
区间修改还是和之前一样,通过差分数组来实现,我们要做的就是实现区间查询

我们知道sum函数得到的是前缀和也就是说:
a[1]+a[2]+……+a[n]
=(c[1])+(c[1]+c[2])+……+(c[1]+c[2]+……+c[n])
=n*c[1]+(n-1)*c[2]+……+c[n]
=n *(c[1]+c[2]+……+c[n])-(0 *c[1]+1 *c[2]+…+(n-1)*c[n])
最终我们就得到了这个至关重要的式子
n *(c[1]+c[2]+……+c[n])-(0 *c[1]+1 *c[2]+…+(n-1)*c[n])

值得注意的是

因为c[]数组是差分数组,所以a[n] = c[1] + c[2] +……+c[n]。
我们最终需要得到的是 a[n]也就是原数组,而不是差分数组!!
在区间修改,单点查询中,我们最终得到单点只需要sum(n)也是因为 a[n] = c[1] + c[2] +……+c[n]。
★sum(n) = c[1] + c[2] +……+ c[n]。
最初我就是在这里产生了疑惑,因为理解成了a[]数组。
然后我们返回看这个关键点:

 n *(c[1]+c[2]+……+c[n])-(0 *c[1]+1 *c[2]+...+(n-1)*c[n]) 

观察这个式子我们其实只需要 开两个数组,一个表示(c[1]+c[2]+……+c[n]),另一个表示(0 *c[1]+1 *c[2]+…+(n-1)*c[n]) 就可以了。

	for(ll i=1;i<=n;i++)
	{
		now = read();
		add(c1,i,now-last);
		add(c2,i,(i-1)*(now-last));
		last = now;
	}

c1数组表示第一个差分数组,add数组的使用和区间修改,单点查询是一样的。观察c2数组就是表示(0 *c[1]+1 *c[2]+…+(n-1)*c[n]) 所以我们只要在输入的时候,再×(i-1)就可以得到这个差分数组了

在后面添加数的时候也要注意这一点

a=read(),b=read(),v=read();	
add(c1,a,v);add(c1,b+1,-v);
add(c2,a,v*(a-1));add(c2,b+1,-v*b);

再数前面乘上位置-1。其他的操作和区间修改,单点查询是一样的。

		a = read(),b = read();
		sum1=(a-1)*sum(c1,a-1)-sum(c2,a-1);
		sum2=b*sum(c1,b)-sum(c2,b);
		printf("%lld\n",sum2-sum1);	

最后我们只要按照之前的式子对sum进行操作就好了,操作一次我们得到的是a[1]+a[2]+……+a[n],很显然如果是要求[3,5]是不可以的,所以我们添加了另一个求a[1]+a[2]的sum2。实际上就是对这个数组进行前缀和求某个区间的操作。

以上只是鄙人的拙见,如果有错误、不足之处,还请指正。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值