1. 程式人生 > >樹狀陣列的基礎

樹狀陣列的基礎

一.樹狀陣列引入.

樹狀陣列是一種玄學的高階資料結構.

這種資料結構基於位運算的思想,通過二進位制位合理分配每一個點應該表示一個什麼區間.

樹狀陣列可以說是線性資料結構,也可以說是樹形資料結構,因為這種資料結構確實可以以一張圖的形式畫出來,但是使用陣列儲存.

 

二.樹狀陣列概述.

樹狀陣列基於一個運算lowbit,這個運算的作用是取出一個數最低位的1.

比如說x=(10010110)_2,那麼lowbit(x)=(00000010)_2.

這個運算的實現非常容易,直接要將除了最後一位1以外的數全部取0,其實就是一個數按位與它的補碼,而計算機中取負其實就是補碼,所以我們可以直接用這樣的式子表示lowbit運算:

lowbit(x)=x\&-x.

寫成程式碼就是:

int lowbit(int x){return x&-x;}

有了這個運算我們就可以設計一個數組C,其中C[x]表示區間(x-lowbit(x),x]的資訊並.

然後就會發現把C其實可以這樣畫成一棵樹(以維護區間[1,8]為例):

其實這就像一棵減少了很多節點的線段樹,但是線段樹可以直接湊出任意區間,但是樹狀陣列只能拼湊出一個以1為左端點的區間,想要求任意區間[l,r]的答案,只能通過求出區間[1,l][1,r]的答案做差獲得.

上述的問題說明如果要維護任意區間的資訊,樹狀陣列維護的資訊除了得滿足區間加法性質,還要滿足區間減法性質.

 

三.詳解樹狀陣列的單點修改與區間查詢.

樹狀陣列可以幹什麼?可以單點修改與區間查詢!

樹狀陣列單點修改x時,除了直接修改c[x],還需要修改它父親的資訊(詳情見上面的樹),x的父親可以通過加上lowbit(x)實現.

那麼我們以將點x加上v為例,就可以這麼寫(lowbit就直接寫x&-x了):

void Add(int x,int v){
  for (;x<=n;x+=x&-x)
    c[x]+=v; 
}

而查詢區間[1,x],通過觀察上面的樹,我們發現只需要從點x開始,不斷得減去lowbit(x),就能將區間[1,x]的資訊取出來.

那麼我們以求區間[1,x]的和為例:

int Query(int x){
  int sum=0;
  for (;x>=0;x-=x&x)
    sum+=c[x];
  return sum;
}

很明顯這兩個操作的時間複雜度均為O(logn).

 

四.樹狀陣列的初始化.

樹狀陣列如何初始化呢?

我們可以直接通過n次add初始化,也可以維護一個字首和來初始化.

其實很容易吧,獻上兩種初始化:

void Build1(){      //n次add 
  for (int i=1;i<=n;++i)
    Add(i,a[i]);
}

void Build2(){      //字首和 
  for (int i=1;i<=n;++i){
    a[i]+=a[i-1];
    c[i]=a[i]-a[i-(i&-i)];
  }
}

 

五.樹狀陣列拓展應用.

樹狀陣列看起來是不能維護區間修改的呢...

但是我們可以通過差分讓它能夠實現區間修改,單點查詢.

具體就是將原陣列差分(設差分陣列為A,那麼A[x]=a[x]-a[x-1]),我們就可以將區間修改轉變為兩個單點修改,讓單點查詢變為查詢字首和.

具體程式碼看例題吧.

 

六.例題與程式碼.

題目1:luogu3374.

程式碼:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000;

int n,a[N+9],c[N+9],m;

void Build(){for (int i=1;i<=n;++i) a[i]+=a[i-1],c[i]=a[i]-a[i-(i&-i)];}
void Add(int x,int num){for (;x<=n;x+=x&-x) c[x]+=num;}
int Query(int x){int sum=0;for (;x>0;x-=x&-x) sum+=c[x];return sum;}

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%d",&a[i]);
}

Abigail work(){
  Build();
}

Abigail getans(){
  int opt,x,y; 
  for (int i=1;i<=m;++i){
    scanf("%d",&opt);
    if (opt==1){
      scanf("%d%d",&x,&y);
      Add(x,y); 
    }else{
      scanf("%d%d",&x,&y);
      printf("%d\n",Query(y)-Query(x-1)); 
    }
  }
} 

int main(){
  into();
  work();
  getans();
  return 0;
} 

題目2:luogu3368.

程式碼:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500000;

int n,a[N+9],c[N+9],m;

void Build(){for (int i=1;i<=n;++i) c[i]=a[i]-a[i-(i&-i)];}
void Add(int x,int num){for (;x<=n;x+=x&-x) c[x]+=num;}
int Query(int x){int sum=0;for (;x>0;x-=x&-x) sum+=c[x];return sum;}

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i)
    scanf("%d",&a[i]);
}

Abigail work(){
  Build();
}

Abigail getans(){
  int opt,x,y,k; 
  for (int i=1;i<=m;++i){
    scanf("%d",&opt);
    if (opt==1){
      scanf("%d%d%d",&x,&y,&k);
      Add(x,k);Add(y+1,-k); 
    }else{
      scanf("%d",&x);
      printf("%d\n",Query(x)); 
    }
  }
} 

int main(){
  into();
  work();
  getans();
  return 0;
}