1. 程式人生 > >資料結構-樹狀陣列(三)

資料結構-樹狀陣列(三)

學習筆記-樹狀陣列(三)

樹狀陣列(一)

樹狀陣列(二)

通過樹狀陣列的基本操作,我們可以實現區間查詢和單點修改。結合差分,又可以實現單點查詢和區間修改。那麼,怎麼才能像線段樹一樣,快速實現區間查詢,區間修改呢?

由差分到字首和

既然要區間修改,那麼一定要使用差分陣列而不是原始陣列

由上一篇可見,c1+c2+...+ci=ai

也就是說,差分陣列的字首和=原數值

而原數值的字首和=字首和

所以字首和=差分陣列的字首和的字首和

雖然聽起來有點繞,但是結論是這樣

簡單嘗試推導

那麼,可以推出如下算式

 a1+a2+a3+...+ai

=(c1)+(c1+c2)+(c1

+c2+c3)+...+(c1+c2+...+ci)

=c1*(i)+c2*(i-1)+c3*(i-2)+...+ci-1*2+ci*1

可以看出來,差分陣列的第j個數會被加i-j+1次

...然而貌似還是很不方便

進一步推導

運用一些簡單的數學知識(這塊我也講不明白),最後推導完畢的式子是這樣:

 a1+a2+...+ai

=(i+1)*(c1+c2+...+ci)-(c1*1+c2*2+...+ci*i)     ——如果你不想寫線段樹,請牢記這個推導式

經過各種玄學等式變形,我們得到了這樣一個算式,可以保證差分陣列的第i個數被加i遍。

這樣,只要建立兩個差分陣列的樹狀陣列,一個儲存ci、一個儲存ci*i,就可以區間修改查詢了。

結論(畫重點)

為了方便呼叫,我們把樹狀陣列建成二維tree[0][i]表示ci,tree[1][i]表示ci*i

那麼在使用單點修改函式對差分陣列單點修改時,要在tree0,i加k,在tree1,i加i*k

想要查詢字首和時,就可以計算(i+1)*(tree0,i的字首和)-(tree1,i的字首和)

程式實現 

區間修改

我們在基礎程式上加一維f,作為區分ci和ci*i兩陣列的變數

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

然後把它套在前面加粗的那個斜體結論上

1 void update(int x, int k)
2 {
3     add(x, k, 0);
4     add(x, x*k, 1);
5 }

 

這就是完整的對差分陣列單點修改函式

那麼,使用差分陣列的O(1)修改的特點,在主程式裡進行兩次update操作,就可以完成區間修改

比如要對x到y加上k,就可以這樣

update(x, k);
update(y+1, -k);

 區間查詢

和區間修改的變化一樣的步驟,首先新增維度

1 int sch(int x, bool f){
2     int ans = 0;
3     while(x >= 1){
4         ans += tree[f][x];
5         x -= lowbit(x);
6     }
7     return ans;
8 }

然後套用結論

1 int sum(int x)
2 {
3     int ans = (x + 1) * (sch(x, 0)) - sch(x, 1);
4     return ans;
5 }

最後,利用字首和陣列的O(1)查詢的特點,在主程式裡面呼叫兩次sum,就可以了

比如要求出x到y的區間和,賦值給res,就可以這樣

res = sum(y) - sum(x - 1);

例題 

LGOJ-P3372

題目大意

實現區間修改和查詢

輸入輸出格式

輸入格式:

第一行包含兩個整數N、M,分別表示該數列數字的個數和操作的總個數。

第二行包含N個用空格分隔的整數,其中第i個數字表示數列第i項的初始值。

接下來M行每行包含3或4個整數,表示一個操作,具體如下:

操作1: 格式:1 x y k 含義:將區間[x,y]內每個數加上k

操作2: 格式:2 x y 含義:輸出區間[x,y]內每個數的和

輸出格式:

輸出包含若干行整數,即為所有操作2的結果。

程式實現

 1 #include<cstdio>
 2 #include<cctype>
 3 #define ll long long
 4 using namespace std;
 5 ll n, m, tree[2][100010];
 6 inline ll read(){
 7     char ch = getchar();
 8     ll f = 1, x = 0;
 9     while(!isdigit(ch)){
10         if(ch == '-') f = -1;
11         ch = getchar();
12     }
13     while(isdigit(ch)){
14         x *= 10;
15         x += (ch & 15);
16         ch = getchar();
17     }
18     return f * x;
19 }
20 ll lowbit(ll x){
21     return x & (-x);
22 }
23 void add(ll x,ll k,ll f)
24 {
25     while(x<=n)
26     {
27         tree[f][x]+=k;
28         x+=lowbit(x);
29     }
30 }
31 void update(ll x, ll k)
32 {
33     add(x, k, 0);
34     add(x, x*k, 1);
35 }
36 ll sch(ll x, ll f){
37     ll ans = 0;
38     while(x >= 1){
39         ans += tree[f][x];
40         x -= lowbit(x);
41     }
42     return ans;
43 }
44 ll sum(int x)
45 {
46     ll ans = (x + 1) * (sch(x, 0)) - sch(x, 1);
47     return ans;
48 }
49 int main(){
50     n = read(), m = read();
51     for(ll i = 1, a = 0, b = 0; i <= n; i++){
52         b = read();
53         update(i, b - a);
54         a = b;
55     }
56     while(m--){
57         if(read() == 1){
58             ll x, y, z;
59             x = read(); y = read(); z = read();
60             update(x, z); update(y + 1, -z);
61         }
62         else{
63             ll x, y;
64             x = read(); y = read();
65             printf("%lld\n", (sum(y) - sum(x - 1)));
66         }
67     }
68     return 0;
69 }