原理:
如图:
黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有
C[1] = A[1];
C[2] = A[1] + A[2];
C[3] = A[3];
C[4] = A[1] + A[2] + A[3] + A[4];
C[5] = A[5];
C[6] = A[5] + A[6];
C[7] = A[7];
C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
可以发现,这颗树是有规律的
C[i] = A[i - 2k+1] + A[i - 2k+2] + … + A[i]; //k为i的二进制中从最低位到高位连续零的长度
例如i = 8(1000)时候,k = 3,可自行验证。
这个怎么实现求和呢,比如我们要找前7项和,那么应该是SUM = C[7] + C[6] + C[4];
而根据上面的式子,容易的出SUMi = C[i] + C[i-2k1] + C[(i - 2k1) - 2k2] + …;
其实树状数组就是一个二进制上面的应用。
现在问题就是,如何求2k
这里,很神奇,2k=2k = i&(-i)
证明如下:
而且这个有一个专门的称呼,叫做lowbit,即取2^k。
这样,单点修改和区间查询就变得简单了,而区间修改和单点查询,只需要引入一点查分的思想
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000005],c[1000005];
//求x二进制下连续0个数
int lowbit(int x){
return x&(-x);
}
//更新a[x]使之增加k
void update(int x,int k){
while(x<=n){
c[x]+=k;
x+=lowbit(x);
}
return;
}
//求a[1]~a[x]之和
int summary(int x){
int ans=0;
while(x>0){
ans+=c[x];
x-=lowbit(x);
}
return ans;
}
/*单点更新,区间求和
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
update(i,a[i]);
}
for(int i=1;i<=m;i++){
int b,x,k;
scanf("%d%d%d",&b,&x,&k);
if(b==1) update(x,k);
else printf("%d\n",summary(k)-summary(x-1));
}
return 0;
}*/
//区间更新,单点求值
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++){
int b,x,y,k;
scanf("%d",&b);
if(b==1){
scanf("%d%d%d",&x,&y,&k);
update(x,k);//更新a[i]-a[i-1]增加k
update(y+1,-k);//更新a[y+1]-a[y]减少k
}
else{
scanf("%d",&x);
printf("%d\n",summary(x));
}
}
return 0;
}