树状数组
树状数组和线段树比较相似,又有所不同。
我甚至一度认为他们一样的,其实不是一样的
见图:可以很快发现这和线段树不一样
这个树是不是很有规律:(num是树状数组,A是原来的数组)
num[1] = A[1]
num[2] = A[1]+A[2]
num[3] = A[3]
num[4] = A[1]+A[2]+A[3]+A[4]
……
在总结大量规律的时候,可以发现
num[i] = A[i - 2^k+1] + A[i - 2^k+2] + … + A[i];
k为i的二进制中从最低位到高位连续零的长度
有人问:如何求k,前辈给出的答案是:x&(-x)
这个时候我们会使用到位运算来辅助我们计算
这里利用的负数的存储特性,负数是以补码存储的,对于整数运算 x&(-x)有:
1.如果一开始是0,那么x&(-x) = 0
2.如果是奇数,负数取反加一。比如 :
7 ---------> 0111
-7---------> 1001
奇数最后一位是1,奇数的负数是取反加1,最后一位肯定是1,结果是1
3.如果是偶数的话:
6----------->0110
-6---------->1010
偶数的时候取反加一的时候会在最后出现的1一样,只有这样子才可以加起
来取模为0,这时候得出来最后得零得长度,k出来了
到这里你应该很快明白了如何构造了
首先lowbit(上面得操作,叫做lowbit)
int lowbit(int x)
{
return x&(-x);
}
void updata(int i,int k)
{
while(i <= n){
num[i] += k;
i += lowbit(i);
}
}
int getsum(int i){ //求A[1 - i]的和
int res = 0;
while(i > 0){
res += num[i];
i -= lowbit(i);
}
return res;
}
出题目的人员很喜欢这种问法:
区间更新、单点查询:
建议使用差分建树
区间更新、区间查询:
差分建树
A[0]+A[1]+……+A[n]
=num[0]+(num[0]+num[1])+……+(num[0]+……+num[n])
=n * (num[0] + …… + num[n]) - (num[1] * 0 + num[2]* 1+……+num[n]*(n-1))
只需要维护两个树状数组就好了