前置芝士
引入
先看例题:(洛谷 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[i−1](1<i≤n)
那这有什么用呢?我们可以发现一个性质:
a [ i ] = ∑ k = 1 n b k a[i]=\sum\limits_{k=1}^n b_k a[i]=k=1∑nbk
联系一下树状数组,你想到了什么?
对了!可以把单点查询转化为差分数组的区间查询!
那么区间修改又怎么解决呢?别着急,我们来看第二个性质:
我们设一个数组 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;
}