資料結構-樹狀陣列(三)
學習筆記-樹狀陣列(三)
樹狀陣列(一)
樹狀陣列(二)
通過樹狀陣列的基本操作,我們可以實現區間查詢和單點修改。結合差分,又可以實現單點查詢和區間修改。那麼,怎麼才能像線段樹一樣,快速實現區間查詢,區間修改呢?
由差分到字首和
既然要區間修改,那麼一定要使用差分陣列而不是原始陣列
由上一篇可見,c1+c2+...+ci=ai
也就是說,差分陣列的字首和=原數值
而原數值的字首和=字首和
所以字首和=差分陣列的字首和的字首和
雖然聽起來有點繞,但是結論是這樣
簡單嘗試推導
那麼,可以推出如下算式
a1+a2+a3+...+ai
=(c1)+(c1+c2)+(c1
=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);
例題
題目大意
實現區間修改和查詢
輸入輸出格式
輸入格式:
第一行包含兩個整數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 }