1. 程式人生 > >題解 P3374 【【模板】樹狀陣列 1】

題解 P3374 【【模板】樹狀陣列 1】

恩,這是AC的第一道樹狀陣列呢。

本蒟蒻以前遇到RMQ問題一般都用線段樹或ST表,可惜ST表不支援線上修改,而線段樹程式碼量又太大。

如今終於找到了折中方案:樹狀陣列!!!!
程式碼量小,還支援修改!


 

樹狀陣列也就是二叉索引樹,又被稱為Fenwick樹,然而我個人認為它不能被嚴謹地成為樹,因為充其量只是借用的樹形結構的思想,於實現上有著較大的區別。

樹狀陣列雖然運用範圍沒有線段樹那麼廣,但是它的效率要高很多,比如線段樹是nlogn,但樹狀陣列是logn。

還有一點需要注意的是:樹狀陣列可以區間查詢,但不能運用於任意區間查詢。這一點在後面會提到。


 

那麼這個樹狀陣列的基本思路就是

用節點ci儲存和,比如:

- c1=a1
- c2=a1+a2
- c3=a3
- c4=a1+a2+a3+a4
- c5=a5
- c6=a5+a6
- c7=a7
- c8=a1+...+a8

當然這樣子可能不是很容易看出內在的聯絡,因此不妨將其轉化為二進位制來觀察:

- c0001=a0001
- c0010=a0001+a0010
- c0011=a0011
- c0100=a0001+a0010+a0011+a0100
- c0101=a0101
- c0110=a0101+a0110
- c0111=a0111
- c1000=a0001+...+a1000

是不是發現了什麼?

沒有嗎?好吧。

事實上這裡的規律就是cn=a(n–2^k+1)+...+an,這裡的k指的是n二進位制末尾0的數量

獲取2^k的操作我們稱之為lowbit,其實現如下:

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

有了lowbit操作之後,求和就很好寫了:

1 int query(int x){
2     int ans=0;
3     while(x!=0){
4         ans+=tree[x];
5         x-=lowbit(x);
6     }
7     return ans;
8 }

要注意一點,這裡求的ans是區間[1,x]的和,想要[y,x]的和只能query(x)-query(y-1)。

因此我們回到了之前那個問題:樹狀陣列不能解決所有區間查詢,**它只能解決如上的有關聯的區間查詢。**

emmmm.....還有update操作:

1 void update(int x,int k){
2     while(x<=n){
3         tree[x]+=k;
4         x+=lowbit(x);
5     }
6 }

這個在明白了樹狀陣列的本質之後也很好理解,就不多做敘述了。


 

總的來說,樹狀陣列挺好用的,值得一學。但切記,無論如何都必須掌握線段樹,因為能用樹狀陣列解決的都能用線段樹,而反之不一定如此。

另附AC程式碼見下:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 
 6 const int maxn=500500;
 7 
 8 int n,m;
 9 int tree[maxn<<2];
10 
11 int lowbit(int k){
12     return k&(-k);
13 }
14 
15 void update(int x,int k){
16     while(x<=n){
17         tree[x]+=k;
18         x+=lowbit(x);
19     }
20 }
21 
22 int query(int x){
23     int ans=0;
24     while(x!=0){
25         ans+=tree[x];
26         x-=lowbit(x);
27     }
28     return ans;
29 }
30 
31 int main(){
32     scanf("%d%d",&n,&m);
33     for(int i=1;i<=n;i++){
34         int a;
35         scanf("%d",&a);
36         update(i,a);
37     }
38     for(int i=1;i<=m;i++){
39         int a,b,c;
40         scanf("%d%d%d",&a,&b,&c);
41         if(a==1)update(b,c);
42         else printf("%d\n",query(c)-query(b-1));
43     }
44 }

恩,這是AC的第一道樹狀陣列呢。
本蒟蒻以前遇到RMQ問題一般都用線段樹或ST表,可惜ST表不支援線上修改,而線段樹程式碼量又太大。。
如今終於找到了折中方案:**樹狀陣列!!!!**###### 程式碼量小,還支援修改!

------------
**樹狀陣列**也就是**二叉索引樹**,又被稱為**Fenwick樹**,然而我個人認為它不能被嚴謹地成為樹,因為充其量只是借用的樹形結構的思想,於實現上有著較大的區別。
樹狀陣列雖然運用範圍沒有線段樹那麼廣,但是它的效率要高很多,比如線段樹是nlogn,但樹狀陣列是logn。
還有一點需要注意的是:樹狀陣列可以區間查詢,但不能運用於任意區間查詢。這一點在後面會提到。

------------
那麼這個樹狀陣列的基本思路就是
![](http://p0.so.qhimgs1.com/bdr/_240_/t01b3cd94b11782f024.png)
用節點ci儲存和,比如:
- c1=a1- c2=a1+a2- c3=a3- c4=a1+a2+a3+a4- c5=a5- c6=a5+a6- c7=a7- c8=a1+...+a8
當然這樣子可能不是很容易看出內在的聯絡,因此不妨將其轉化為二進位制來觀察:
- c0001=a0001- c0010=a0001+a0010- c0011=a0011- c0100=a0001+a0010+a0011+a0100- c0101=a0101- c0110=a0101+a0110- c0111=a0111- c1000=a0001+...+a1000
是不是發現了什麼?
沒有嗎?好吧。
事實上這裡的規律就是**cn=a(n–2^k+1)+...+an**,這裡的k指的是**n二進位制末尾0的數量**。
獲取2^k的操作我們稱之為lowbit,其實現如下:
```cppint lowbit(int k){return k&(-k);}```
有了lowbit操作之後,求和就很好寫了:
```cppint query(int x){int ans=0;while(x!=0){ans+=tree[x];x-=lowbit(x);}return ans;}```
要注意一點,這裡求的ans是區間[1,x]的和,想要[y,x]的和只能query(x)-query(y-1)。
因此我們回到了之前那個問題:樹狀陣列不能解決所有區間查詢,**它只能解決如上的有關聯的區間查詢。**
emmmm.....還有update操作:
```cppvoid update(int x,int k){while(x<=n){tree[x]+=k;x+=lowbit(x);}}```
這個在明白了樹狀陣列的本質之後也很好理解,就不多做敘述了。

------------
總的來說,樹狀陣列挺好用的,值得一學。但切記,**無論如何都必須掌握線段樹**,因為能用樹狀陣列解決的都能用線段樹,而反之不一定如此。
另附AC程式碼見下:
```cpp#include<iostream>#include<cstdio>#include<algorithm>using namespace std;
const int maxn=500500;
int n,m;int tree[maxn<<2];
int lowbit(int k){return k&(-k);}
void update(int x,int k){while(x<=n){tree[x]+=k;x+=lowbit(x);}}
int query(int x){int ans=0;while(x!=0){ans+=tree[x];x-=lowbit(x);}return ans;}
int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){int a;scanf("%d",&a);update(i,a);}for(int i=1;i<=m;i++){int a,b,c;scanf("%d%d%d",&a,&b,&c);if(a==1)update(b,c);else printf("%d\n",query(c)-query(b-1));}}```