参考:
https://www.byvoid.com/blog/binary-index-tree
http://www.cnblogs.com/sixdaycoder/p/4348360.html
http://blog.csdn.net/liangzhaoyang1/article/details/51365192
1)求Ci
C[i] = A[i-2^k+1] + .........+A[i]。
其中k表示,将i转化为2进制后,从右往左数,0的个数。
C1 = A1
C2 = C1 + A2 = A1 + A2
C3 = A3
C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
C5 = A5
C6 = C5 + A6 = A5 + A6
C7 = A7
C8 = C4 + C6 + C7 + A8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
按照C[i] = A[i-2^k+1] + .........+A[i]计算Ci,会产生一些重复计算。如求C8时,C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8。其中A1 + A2 + A3 + A4=C4、 A5 + A6=C6、A7=C7在前面我们已经计算过了,所以我们可以先计算C8的所有子节点,然后将子节点的值累加,这是动态规划的思想,动态规划的核心思想就是避免子问题重复计算。C8的子节点有C4、C6、C7、A8。那么怎么求子节点呢?见下面。
2)求Ci的子节点。
8的二进制形式是1000。对其右移操作,得0100,此时k=2,8-2^2=4,C4。再右移的0010,k=1,8-2^1=6,C6。继续右移0001,k=0,8-2^0 = 7, C7。再加上其本身A8,就得到C8了。再如6,110。右移011,k=0,6-2^0 = 5,C5。再加上A6即为C6。再如7,111。因为其末尾已经没有0了,不用右移,直接是A7。
1)和2)结合求Ci的代码:
vector<int> build_BIT(vector<int> &nums) {
vector<int> BIT;//树状数组(二分索引树)
int size = nums.size(), temp, s;
for (int i = 1; i <= size; ++i){ //构建BIT
temp = nums[i-1];
s = i;
while (s%2!=1){
s = s>>1;
temp += BIT[i - lowbit(s) - 1];
}
BIT.push_back(temp);
}
return BIT;
}
3)求Ci的父节点。
C [i+2^k]即为Ci的父节点,如C4,4=0100,k=2,所以i+2^k = 4+2^2 = 8, 即C8。如C5,5=0101,k=0,所以i+2^k = 5+2^0 = 6, 即C6。
4)怎么求k( 也即怎么求2^k )?
2^k=k&(~k+1),根据补码的性质化简为k&(-k); 这里上面有篇文章里面讲的很细点击打开链接。
5)怎么求前缀和,这才是树状数组要解决的问题。
sum(i) = sum( i - lowbit(i) ) + C[i]
由于C[i]已知,所以sum(i)可以通过递归求解,递归出口为当i = 0时。如sum(8) = sum(8-8) + C8 = C8; sum(6) = sum(6-2^1) + C6 = sum(4-2^2) + C4 +C6 = C4 +C6。
<span style="font-size:18px;">int sum(int p)
{
int rs=0;
while (p)
{
rs+=C[p];
p-=lowbit(p);
}
return rs;
}</span>
6) 更新Ai以及Ci假如Ai的现在的值为Ai’,则delta = Ai' - Ai。那么改变这个值,会影响哪些C呢?
会改变Ci的父节点、父节点的父节点,具体代码如下:
<span style="font-size:18px;">void modify(int p,int delta)
{
while (p<=N)
{
C[p]+=delta;
p+=lowbit(p);
}
}</span>