1. 程式人生 > >【資料結構】——樹狀陣列的幾種模型

【資料結構】——樹狀陣列的幾種模型

樹狀陣列

基本定義:樹狀陣列是利用二分的思想使得查詢和修改的複雜度都為log(n)的資料結構,主要用於查詢陣列字首和、區間和並且經常更改資料。
樹狀陣列的儲存方式

資料結構思想:如上圖,2的k次方的位置存放1一直到2k這些數的和,然後再不斷二分。具體實現可以用二進位制解釋,也就是例如XXX100中儲存的是XXX000~XXX100這一個區間的所有數

基本操作:而要實現這一點,就要求一個二進位制數的最低位1,這個可以用lowbit操作實現:
一個二進位制數x對其進行x&(-x)的操作,就可以保留其最低位的1,而講其他全部位全清零
所以一個數加上自己的lowbit,就到了上一級包含自己的區間,例如110(6)加上10變成了1000(8),因為1000對應的0~1000
同樣,減去自己的lowbit就相當於去尾。

模型1:改點求點求段

//設M為最大數的上限,treeray存樹狀陣列
void add(int k,int num)//像某個位置新增的操作
{
    while(k<=M)//防止上界溢位
    {
        treeray[k]+=num;
        k+=k&(-k);//不斷加上lowbit來向上更新包含自己的區間
    }
    return;
}

int read(int k)//讀取以某個位置為終點的字首和
{
    int sum=0;
    while(k)//一定要注意樹狀陣列不能儲存0這個位置
    {
        sum+=treeray[k];
        k-=k&(-k);//不斷減lowbit來加上前面區間的和
} return sum; }

模型2:改段求點
當需要改段求點時,利用樹狀陣列方便求字首和的特性,我們採用記錄變化量的技巧,就可以使得一個數的字首和變成他之前所有的變化量,便可得到這個數本身。
在樹狀陣列中,每個數初始化為0,然後每個位置記錄它與左邊的差值,如圖所示:

//add與read函式同上
//當在a與b間全部加上c時
add(a,c);
add(b+1,c);
//當要得到k的值時
read(k);

模型3:改段求段
當需要改段求段時,與前一種型別的區別是,需要求某一點“真正的”字首和。考慮前一種方法,只知道該點本身與前面所有變化量的總和,卻不知道這些變化是從哪裡開始的,無法方便地求出字首和。我們先假設這個點之前所有數都與這個點相等,這樣必然會多出前面的一些變化值變化長度這麼多,那麼我們再用一個樹狀陣列,其中在每一個變化點記錄變化值

變化長度,那麼最終算某個點的和,只需要再減去這個樹狀陣列的在該點的值,如圖所示:

int A[],B[]; //兩個樹狀陣列
void tadd(int a[],int x,int c)
{
    while(x<=N)
    {
        a[x]+=c;
        x+=lowbit(x);
    }
    return;
}

int tread(int a[],int x)
{
    int sum=0;
    while(x>0)
    {
        sum+=a[x];
        x-=lowbit(x);
    }
    return sum;
}

void update(int a,int b,int c)
{
    tadd(A,a,c);
    tadd(A,b+1,-c);
    tadd(B,a,c*(a-1)); //疊加字首變化量
    tadd(B,b+1,-c*b);
    return;
}

int querry(int a,int b)
{
    int sum1=tread(A,a-1)*(a-1)-tread(B,a-1);
    int sum2=tread(A,b)*b-tread(B,b);
    return sum2-sum1;
}

模型4:多維樹狀陣列(以二維為例)
多維樹狀陣列只需把多維的每一維度分別當作一維樹狀陣列即可,那麼N維的某一點的字首和,其實就是,各個維度的字首和分別對映到其他維度的字首和
所以這裡給出二維樹狀陣列的程式碼實現,具體可以見下面的例題POJ1195:

void tadd(int i,int j,int c)
{
    for(int x=i;x<=N;x+=lowbit(x))
        for(int y=j;y<=N;y+=lowbit(y))//一定要注意這裡不能再直接用傳入的形參j了,因為每次迴圈都要使之成為剛傳入的值
            base[x][y]+=c;
    return;
}

int tread(int i,int j)
{
    int sum=0;
    for(int x=i;x>0;x-=lowbit(x))
        for(int y=j;y>0;y-=lowbit(y))
            sum+=base[x][y];
    return sum;
}

注意提示
1.樹狀陣列中一定不能有0元,如果題目中有要注意處理。通常是整體資料加1,這樣後同時也要注意變化後的資料上限
2.樹狀陣列可以用來求逆序數,實現方法使讓數字成為樹狀陣列的元素,比某個數小的數有多少就是這個數在陣列中的字首和