樹狀陣列的基礎
一.樹狀陣列引入.
樹狀陣列是一種玄學的高階資料結構.
這種資料結構基於位運算的思想,通過二進位制位合理分配每一個點應該表示一個什麼區間.
樹狀陣列可以說是線性資料結構,也可以說是樹形資料結構,因為這種資料結構確實可以以一張圖的形式畫出來,但是使用陣列儲存.
二.樹狀陣列概述.
樹狀陣列基於一個運算lowbit,這個運算的作用是取出一個數最低位的1.
比如說,那麼.
這個運算的實現非常容易,直接要將除了最後一位1以外的數全部取0,其實就是一個數按位與它的補碼,而計算機中取負其實就是補碼,所以我們可以直接用這樣的式子表示lowbit運算:
.
寫成程式碼就是:
int lowbit(int x){return x&-x;}
有了這個運算我們就可以設計一個數組C,其中表示區間的資訊並.
然後就會發現把C其實可以這樣畫成一棵樹(以維護區間[1,8]為例):
其實這就像一棵減少了很多節點的線段樹,但是線段樹可以直接湊出任意區間,但是樹狀陣列只能拼湊出一個以1為左端點的區間,想要求任意區間的答案,只能通過求出區間和的答案做差獲得.
上述的問題說明如果要維護任意區間的資訊,樹狀陣列維護的資訊除了得滿足區間加法性質,還要滿足區間減法性質.
三.詳解樹狀陣列的單點修改與區間查詢.
樹狀陣列可以幹什麼?可以單點修改與區間查詢!
樹狀陣列單點修改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;
}
而查詢區間,通過觀察上面的樹,我們發現只需要從點x開始,不斷得減去lowbit(x),就能將區間的資訊取出來.
那麼我們以求區間的和為例:
int Query(int x){ int sum=0; for (;x>=0;x-=x&x) sum+=c[x]; return sum; }
很明顯這兩個操作的時間複雜度均為.
四.樹狀陣列的初始化.
樹狀陣列如何初始化呢?
我們可以直接通過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,那麼),我們就可以將區間修改轉變為兩個單點修改,讓單點查詢變為查詢字首和.
具體程式碼看例題吧.
六.例題與程式碼.
題目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;
}