樹狀數組

1、概述

樹狀數組(binary indexed tree),是一種設計新穎的數組結構,它能夠高效地獲取數組中連續n個數的和。概括說,樹狀數組通常用於解決以下問題:數組{a}中的元素可能不斷地被修改,怎樣才能快速地獲取連續幾個數的和?

2、樹狀數組基本操作

傳統數組(共n個元素)的元素修改和連續元素求和的複雜度分別爲O(1)和O(n)。樹狀數組通過將線性結構轉換成僞樹狀結構(線性結構只能逐個掃描元素,而樹狀結構可以實現跳躍式掃描),使得修改和求和複雜度均爲O(lgn),大大提高了整體效率。

如圖:

C1=A1
C2=A1+A2
C3=A3
C4=A1+A2+A3+A4
C5=A5
C6=A5+A6
C7=A7
C8=A1+A2+A3+A4+A5+A6+A7+A8
 …………
C16=A1+A2+A3+A4+A5+A6+A7+A8+A9+A10+A11+A12+A13+A14+A15+A16

不難發現,C[i] = A[i–2^k+ 1] + … + A[i]其中,k爲i在二進制下末尾0的個數,i從1開始算!

問題到了求2^k了,我們知道,一個正數的相反數的補碼就是這個正數的原碼的反碼再加1,比如:

4的補碼爲0100,-4的補碼即爲1011+1=11005的補碼爲0101,-5的補碼即爲1010+1=1011

6的補碼爲0110,-6的補碼即爲1001+1=10107的補碼爲0111,-7的補碼即爲1000+1=1001

可以看到,每個數與它相反數的補碼相同段剛好就是2^k的補碼!

int Lowbit(int w)
{
    return w&(-w);
}
接下來是求前n項和,比如sum(6) = C[6] +C[4];即 sum(i) = C[i] + C[Lowbit(i)] +....

int sum(int pos)//區間求和,向下查找
{
    int sum = 0;
    while(pos>0)
    {
        sum += tree[pos];
        pos  -= Lowbit(pos);
    }
    return sum;
}
void change(int pos,int num)//單點改值,向上更新,
{
    while(pos<=n)           
    {
        tree[pos] += num;
        pos += Lowbit(pos);
    }
}

傳統的樹狀數組就說這麼多吧,接下來就是線段樹版的樹狀數組了

先看圖,比如求1-4之間的和,先加tree[9]和tree[12]

然後加上tree[5]的和


int tree[2048];//理論上能存放放 [0,2^k) 的樹,其實只能查詢 [1,2^k - 2] 的範圍,但需要開2^(k+1)的空間

void change(int pos,int num)//由底向上
{
    pos += M;
    for(tree[pos]=num,pos >>= 1;pos;pos >>= 1)
        tree[pos] = tree[pos<<1]+tree[pos<<1|1];
}

int presum(int pos)//前綴和
{
    int sum = 0;
    for(pos += (M+1);pos^1;pos >>= 1)
        if(pos&1)                   //如果是右兒子,就加上左兒子的節點和,相當於依次往前面加
            sum += tree[pos-1];
    return sum;
}

int getsum(int l,int r)//區間求和
{
    int sum = 0;
    l--; r++;
    for(l += M,r += M;l^r^1;l >>= 1,r >>=1)
    {
        if((l&1)==0)         //這裏要小心, &的優先級沒==高
            sum += tree[l+1];//如果l是左兒子的話,就加上右兒子的節點和
        if(r&1)
            sum += tree[r-1];//如果r是右兒子,就加上左兒子的節點和
    }
                           //直到l和r成爲兄弟節點爲止(這個過程就相當於一個由區間兩邊往中間加的過程)
    return sum;
}


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