树状数组

#解决的问题
假设我们有这样一个问题:给定a[1],a[2],...a[n],定义两个操作modify,sum

* modify(a[i], val):a[i]=val
* sum(i,j):求a[i]+a[i+1]+...+a[j] 的值

现在希望能高效的处理这两个操作。

最直接的思路是仅仅保存a[1],a[2],...a[n], 这样每次修改复杂度是O(1),而求和复杂度则是O(n)。  
如果我们定义 s[1]=a[1], s[2]=a[1]+a[2], ... s[n]=a[1]+a[2]+...a[n],这样sum的复杂度是O(1),而modify的复杂度则变成了O(n)(考虑修改第一个元素,我们需要对sum数组里每个值更新)

树状数组能够比较好的解决这个问题,它对于modify,sum的复杂度都是O(logn)



# 分离出最后的1
找出数n的二进制表示形式中,从低位往高位数的第一个1。实现方法有两种

* 一直除以2,直到除不了为止。
* lowbit算法

lowbit算法大致想法是,将n对应的二进制先进行翻转(1->0, 0->1), 然后加1(这样翻转后末尾k个连续的1就会进到第k+1位上),然后与开始的n做AND。
```cpp
int lowbit(int n)
{
    return n&(-n); //-n,即对n翻转后+1

}
```
比如对于n=12(0...01100), -n = 1....10011+1=1...10100,n&(-n)=100

# 引入数组c
其中c[i] = a[i]+a[i-1]+...+a[i-2^k+1], k是i的二进制表示中,末尾0的个数

* c[1] = a[1]
* c[2] = a[2] + a[1]
* c[10] = a[10]+a[9]
* c[24] = a[24]+a[23]+a[22]+..+a[17]


注意到,c[i] = a[i]+c[i1]+c[i2]+..,其中i1=i-2^k+2^(k-1), i2=i1+2^(k-2),...,比如
>c[ *11000* ]=a[ *11000* ] + c[ *10100* ]+c[ *10110* ]+c[ *10111* ]
>这里*斜体*表示二进制的表示形式

从而,计算数组c,至多也就只需O(logn)次的加法


# 计算前n项的和
令S[n] = a[1]+a[2]+..+a[n],我们希望将s[n]与c[1],c[2]...c[n]建立起联系,同时希望每一个s[n]与最多O(logn)个c建立联系。
令n的二进制表示中,1所在的位置分别是d1,d2,..dk, 其中最右位是0,
S[n] = c[n] + c[n-2^dk] + c[n-2^dk-2^(d(k-1))+..


n=20=* 10100 * ,  S[* 10100 *] = c[ *10100* ] + c[ *10000 *]
* n=26=* 11010 *,S[* 11010 *] = c[  *11010* ] + c[ *11000 *] + c[*10000*]

有了前n项的快速算法后,计算从a[p]+a[p+1]+..a[q] = S[q] - S[p-1]

# 修改某一项
修改a[i], 相应影响的c是c[p1], c[p2], .. c[pm] , 其中 p1=i, p(i+1)=p(i)+2^{li}, li 是i二进制末尾0的个数
比如, 

* 修改a[6],相应影响的是,c[6], c[8], c[16],...
* 修改a[11], 相应影响的是 c[11], c[12], c[16], c[32], ...

结合**lowbit**的快速算法,无论是计算前n项的和还是修改某一项,都能有一个快速的实现。

考虑到树的高度是log(n), 计算前n项和,修改某一项,复杂度都会是O(logn)




posted on 2014-01-08 10:54  bian  阅读(161)  评论(0编辑  收藏  举报

导航