树状数组详解(区间修改和单点查询)

前置芝士

单点修改和区间查询

引入

先看例题:(洛谷 P3368 【模板】树状数组 2

已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数数加上 x x x
2.求出某一个数的值。

这题和 树状数组1 有什么区别?

没错,正如题目所说,这题是区间修改和单点查询

区间修改和单点查询

区间修改和单点查询要怎么实现呢?这需要用到一个东西:差分。

差分是什么呢?(知道的大佬可以跳过)

对于一个数组 a [ ] a[] a[],我们设它的差分数组为 b [ ] b[] b[],则满足:

b [ 1 ] = a [ 1 ] b[1]=a[1] b[1]=a[1]
b [ i ] = a [ i ] − a [ i − 1 ] ( 1 < i ≤ n ) b[i]=a[i]-a[i-1](1<i≤n) b[i]=a[i]a[i1](1<in)

那这有什么用呢?我们可以发现一个性质:

a [ i ] = ∑ k = 1 n b k a[i]=\sum\limits_{k=1}^n b_k a[i]=k=1nbk

联系一下树状数组,你想到了什么?

对了!可以把单点查询转化为差分数组的区间查询!

那么区间修改又怎么解决呢?别着急,我们来看第二个性质:

我们设一个数组 a [ ] a[] a[]及它的差分数组 b [ ] b[] b[]为:

a [ ] a[] a[] 1 1 1 4 4 4 5 5 5 7 7 7 8 8 8 3 3 3 1 1 1
b [ ] b[] b[] 1 1 1 3 3 3 1 1 1 2 2 2 1 1 1 − 5 -5 5 − 2 -2 2

我们对 a [ ] a[] a[]进行区间修改,将 a [ 3 ] a[3] a[3]~ a [ 5 ] a[5] a[5]的值加上 2 2 2,可以得到:

a [ ] a[] a[] 1 1 1 4 4 4 7 7 7 9 9 9 10 10 10 3 3 3 1 1 1
b [ ] b[] b[] 1 1 1 3 3 3 3 3 3 2 2 2 1 1 1 − 7 -7 7 − 2 -2 2

观察 b [ ] b[] b[]的变化,你发现了什么?

只有 b [ 3 ] b[3] b[3] b [ 6 ] b[6] b[6]发生了变化! b [ 3 ] b[3] b[3]加上了 2 2 2 b [ 6 ] b[6] b[6]加上了 − 2 -2 2

经过多次试验,我们可以得到这样一个性质:

a [ l ] a[l] a[l]~ a [ r ] a[r] a[r]加上 k k k,等价于将 b [ l ] b[l] b[l]加上 k k k,将 b [ r + 1 ] b[r+1] b[r+1]加上 − k -k k

再联系一下树状数组,你又想到了什么?

我们可以将区间修改转化为差分数组的单点修改!

这样,我们就将区间修改和单点查询转化为了我们熟悉的单点修改和区间查询!问题迎刃而解了!

完整代码

#include<iostream>
#include<cstdio>
#define MAXN 500010
using namespace std;
int n,m,op,x,y,k;
int a[MAXN],c[MAXN];
int lowbit(int x)
{
	return x&(-x);
}
void update(int x,int k)
{
	for(;x<=n;x+=lowbit(x))
		c[x]+=k;
}
int query(int x)
{
	int res=0;
	for(;x>=1;x-=lowbit(x))
		res+=c[x];
	return res;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		update(i,a[i]-a[i-1]);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d",&x,&y,&k);
			update(x,k);
			update(y+1,-k);
		}
		else
		{
			scanf("%d",&x);
			printf("%d\n",query(x));
		}
	}
	return 0;
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值