【模板】樹狀數組BIT

講樹狀數組之前,首先要來誇誇樹狀數組。
樹狀數組真的是一種好東西啊~~~代碼極短,又非常好寫,很實用,好東西吶。


誇完了樹狀數組,現在就來講講樹狀數組。
樹狀數組(Binary Indexed Tree),又名二叉索引樹,Fenwick樹,其處理的問題模型一般可以轉化爲如下形式:
定義一個數組 a[1..n] ,並維護一下兩個操作:

  • 修改,給 a[i] 加上某個增量 delta
  • 查詢,詢問某個前綴 a[1..index] 的和,即 i=1indexa[i]

顯然,樸素的算法能夠在 O(1) 的時間內處理修改操作,但對於查詢操作需要 O(n) 的時間複雜度,這在查詢次數較多的情況下是接受不了的。而樹狀數組可以達到在 O(logn) 的時間內完成修改和查詢操作。

我們知道,每個整數可以表示爲若干個2的冪次之和。相似的,對於每次求前綴和,我們也希望能夠將其分解爲一系列恰當的,不相交的”子集”,進而求出它們的和。

舉例來講,7=4+2+1=22+21+20 那麼求前綴和 i=17a[i] ,我們也希望能夠將它分爲 3 個子集的和。一般地,如果前綴下標 index 的二進制中有 m1 , 我們就希望將其分解爲 m 個子集之和。基於這種思想,我們構造如下的表格:

下標 1 2 3 4 5 6 7 8 9 10 11 12
內容 1 1.. 2 3 1.. 4 5 5.. 6 7 1.. 8 9 9.. 10 11 9.. 12

下標代表每個子集的編號,內容表示該”子集”所包含的 a 數組的元素。這樣劃分的用意何在呢?我們來看一個實際的例子:

下標 1 2 3 4 5 6 7 8 9 10 11 12
a數組 2 0 1 1 0 2 3 0 1 0 2 1
前綴和 2 2 3 4 4 6 9 9 10 10 12 13
子集和 2 2 1 4 0 2 3 9 1 1 2 4

這裏不妨用 sum 數組表示子集和,接下來,我們檢驗這種求和方案是否滿足一開始提出來的類比思想。
比如,求前綴和 i=17a[i] , 我們只需計算 sum[7]sum[6]sum[4] ,這三項的和,這三個數組中的具體意義可以參考最前面的一張表格,查表發現這三個數覆蓋了 [1,7] 這個區間,而且三個數的和 3+2+4=9 ,說明這種類比方法是成立的!
用BIT經典照片來更直觀的理解一下”子集和”的意義:
這裏寫圖片描述
如上每個長方形代表每個子集對應的部分和,深色代表自己下標對應的值 a[index] ,淺色部分代表還要維護別的下標對應的值 a[k..index1] 。對於每次查詢,如果我們沿着黑色的直線走下去,就可以得到對應的前綴和。

接下來講實現的過程。
子集的劃分方法
現在的問題就是這種子集的劃分是如何進行的。列一張表格給出子集和其所包含的元素個數之間的關係。

下標 1 2 3 4 5 6 7 8
下標的二進制表示 1 10 11 100 101 110 111 1000
元素個數C 1 2 1 4 1 2 1 8
C的二進制表示 1 10 1 100 1 10 1 1000

分析上表可以發現原色個數的二進制表示就是下標的二進制表示中的最低位所在的位置對應的書。
現在介紹樹狀數組實現過程中一個最重要的技術——低位技術(lowbit )。藉助位運算,我們可以得到許多功能強大的 lowbit() 函數。

  • C(index)=index(index and (index1))
    通過模擬不難發現, index and (index1)index 的最低位1與其之後的0全部變成了0, 這樣子再被 index 減去之後,可以得到我們想要的結果,但是事實上我們更常使用的是下面一種求 lowbit() 的方法。
  • C(index)=index and index
    這裏的 index 要用有符號整形存儲。其次,我們知道計算機是用補碼存儲整數的,正數的補碼就是自身的二進制碼,其相反數的補碼就是用其反碼+1而得。我們假設 index 的二進制可以表示爲 x1y¯ ,其中,y 爲若干個0,1就是其最低位的1,那麼 index 則是 ( ~x¯)1y¯ ,~表示對 x 取非,兩者做 and 操作之後就得到了 1y¯ , 即我們想要的數。

有了上述方法,相信得出模板一定不難。

int c[MAXN];
namespace BIT {
    inline int lowbit(int x) { 
        return x & -x; 
    }
    void update(int x, int d) {  
        for (int i = x; i <= MAXN; i += lowbit(i))
            c[i] += d;
    }
    int query(int x) {  
        int res = 0;  
        for (int i = x; i > 0; i -= lowbit(i))
            res += c[i];
        return res;  
    }  
}

update()query() 分別對應上面兩種操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章