还是先看看基础的树状数组坐标是怎么由二进制进化来的 这个大神写的非常基础
https://www.cnblogs.com/xenny/p/9739600.html
我们就是把数组下标为偶数的推到最顶端
我们可以得到以下的关系式:
- C[1](1) = A[1];
- C[2] (10)= A[1] + A[2];
- C[3] (11)= A[3];
- C[4] (100)= A[1] + A[2] + A[3] + A[4];
- C[5] (101)= A[5];
- C[6] (110)= A[5] + A[6];
- C[7] (111)= A[7];
- C[8] (10000)= A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
- 可以发现,这颗树是有规律的
奇数我们就先不管 我们来观察偶数 我们可以发现偶数坐标满足一下规律
- C[i] = A[i - 2^k+1] + A[i - 2^k+2] + … + A[i]; k为i的二进制中从最低位到高位连续零的长度
我们怎么来获取这个K的值呢?
我们可以发现2^k次方其实就是最低位1的位置
● 这里利用的负数的存储特性,负数是以补码存储的,对于整数运算 x&(-x)有
● 当x为0时,即 0 & 0,结果为0;
●当x为奇数时,最后一个比特位为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。结果为1。
●当x为偶数,且为2的m次方时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2 k); 其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的二进制表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第k+1位因为进位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为0。结果为2^k。
● 总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。
搞懂了树状数组的下标和二进制按位与的关系以后,我们就需要去观察怎样访问C[I],也就是求和了;
- Sum[1]=c[1];
- Sum[2]=c[2];
- Sum[3]=c[2]+c[3];
- Sum[4]=c[4];
- Sum[5]=c[4]+c[5];
- Sum[6]=c[4]+c[6];
- Sum[7]=c[4]+c[6]+c[7];
- Sum[8]=c[8];
- 先列出一个比较特殊的例子吧:i=7时:Sum[7]=c[7]+c[6]+c[4]
- Sum(111)=c(111)+c(110)+c(100)
- 我们可以发现他是更新树状数组的逆过程 就是从i递减 i &(-i)
好了现在我们终于进入到写模板的时刻了
int n;
int a[1005],c[1005]; //对应原数组和树状数组
int lowbit(int x){//其实这个我们可以写到定义函数中 因为进场使用到
return x&(-x);
}
void updata(int i,int k){ //在i位置加上k
while(i <= n){//注意:N是全局变量,并且我们要注意N的范围选取
c[i] += k;
i += lowbit(i);
}
}
int getsum(int i){ //求A[1 - i]的和
int res = 0;
while(i > 0){
res += c[i];
i -= lowbit(i);
}
return res;
}
对于任意一个区间的查询我们又该怎么去做呢?
其实特别简单 我们要求Sum[R-L+1]=Sum[R]-Sum[L-1]
还有就是减法呢 我们就只需要update(n,-x) 也就是加上相反数就可以了