樹狀數組

#解決的問題
假設咱們有這樣一個問題:給定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)


相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息