树状数组维护区间和的模型及其拓广的简单总结
?
by wyl8899???
?
樹狀數組的基本知識已經被講到爛了,我就不多說了,下面直接給出基本操作的代碼。
假定原數組為a[1..n],樹狀數組b[1..n],考慮靈活性的需要,代碼使用int *a傳數組。
#define lowbit(x) ((x)&(-(x)))int sum(int *a,int x){int s=0;for(;x;x-=lowbit(x))s+=a[x];return s;}void update(int *a,int x,int w){for(;x<=n;x+=lowbit(x))a[x]+=w;}sum(x)返回原數組[1,x]的區間和,update(x,w)將原數組下標為x的數加上w。
這兩個函數使用O(操作數*logn)的時間和O(n)的空間完成單點加減,區間求和的功能。
?
接下來做一些升級,讓樹狀數組完成區間加減,單點查詢的功能。
直接做的話很困難,需要對問題做一些轉化。
考慮將原數組差分,即令d[i]=a[i]-a[i-1],特別地,d[1]=a[1]。
此時a[i]=d[1]+..+d[i],所以單點查詢a[i]實際上就是在求d數組的[1..i]區間和。
而區間[l,r]整體加上k的操作,可以簡單地使用d[l]+=k和d[r+1]-=k來完成。
于是,我們用樹狀數組來維護d[],就可以解決問題了。
?
下面再升級一次,完成區間加減,區間求和的功能。
仍然沿用d數組,考慮a數組[1,x]區間和的計算。d[1]被累加了x次,d[2]被累加了x-1次,...,d[x]被累加了1次。
因此得到
sigma(a[i])
=sigma{d[i]*(x-i+1)}
=sigma{ d[i]*(x+1) - d[i]*i }
=(x+1)*sigma(d[i])-sigma(d[i]*i)
所以我們再用樹狀數組維護一個數組d2[i]=d[i]*i,即可完成任務。
POJ 3468就是這個功能的裸題,下面給出代碼。
[請注意我們上面的討論都假定了a[]初始全是0。如果不是這樣呢?下面的程序里給出了一個相對簡便的處理辦法。]
// POJ 3468?? Using BIT#include <cstdio>const int maxn=100010;__int64 a[maxn],b[maxn],c[maxn];int n,m;inline int lowbit(const int &x){return x&(-x);}__int64 query(__int64 *a,int x){__int64 sum=0;while(x){sum+=a[x];x-=lowbit(x);}return sum;}void update(__int64 *a,int x,__int64 w){while(x<=n){a[x]+=w;x+=lowbit(x);}}int main(){int l,r,i;__int64 ans,w;char ch;scanf("%d%d",&n,&m);a[0]=0;for(i=1;i<=n;++i){scanf("%I64d",&a[i]);a[i]+=a[i-1];}while(m--){scanf("%c",&ch);while(ch!='Q' && ch!='C')scanf("%c",&ch);if(ch=='Q'){scanf("%d%d",&l,&r);ans=a[r]-a[l-1]+(r+1)*query(b,r)-l*query(b,l-1)-query(c,r)+query(c,l-1);printf("%I64d\n",ans);}else{scanf("%d%d%I64d",&l,&r,&w);update(b,l,w);update(b,r+1,-w);update(c,l,w*l);update(c,r+1,-(r+1)*w);}}return 0;}[當a[]初始不全0的時候,我們就只維護后來加上去的部分,查詢區間和的時候再補上初始的時候這一段的區間和就可以了。]
======================一維到二維的分割線=========================
接下來到二維樹狀數組。
先看看sum和update變成什么樣子了吧。
inline int gs(int a[maxn][maxn],int x,int y){int s=0,t;for(;x;x-=lowbit(x))for(t=y;t;t-=lowbit(t))s+=a[x][t];return s;}inline void gp(int a[maxn][maxn],int x,int y,int w){int t;for(;x<=n;x+=lowbit(x))for(t=y;t<=m;t+=lowbit(t))a[x][t]+=w;}gs就是sum,gp就是update,由于需要多次調用的緣故,改成了更短的名字。
單點加減,矩形求和并不難,直接用上面的兩段就行了。
需要注意的是矩形的求和怎么求。上面的代碼返回的是(1,1)-(x,y)矩形的和。
那么(x1,y1)-(x2,y2)的矩形和由下式給出:
sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)
畫個圖就很好理解了。
?
對于涉及矩形加減的情形,我們發現一維中的差分的辦法在二維的情況用不出來,所以要改一下。思考一下一維中的差分的另外一個含義:d[i]同時也表示d[i..n]的整體增量,d[i]+=k就意味著把d[i]..d[n]全部加上了k。理解了之后就發現這個意義上可以推廣到二維,仍假設原矩形初始全為0,以便接下來的敘述。
令a[x,y]表示(x,y)-(n,m)矩形的整體增量,其中(n,m)是邊界。
那么(x1,y1)-(x2,y2)矩形整體加k的代碼就是
gp(a,x1,y1,w); gp(a,x2+1,y1,-w);gp(a,x1,y2+1,-w); gp(a,x2+1,y2+1,w);
仍然是建議畫個圖來幫助理解。
?
至此,矩形加減,單點查詢的問題得到了解決。
?
重頭戲在這里,矩形加減,矩形求和。
求原矩形(1,1)-(x,y)的和,結果由下式給出
sigma(i=1..x,j=1..y) a[i,j]*(x-i+1)*(y-j+1)
很好理解吧? 但是這個式子并不是那么容易求和的,展開一下求和的部分得到
a[i,j]*? ( (x+1)(y+1) - (x+1)*j - (y+1)*x + i*j )
整個式子就是
(x+1)(y+1)sigma(a[i,j]) - (x+1)sigma(a[i,j]*j) - (y+1)sigma(a[i,j]*i) + sigma(a[i,j]*i*j)
知道怎么處理了吧?如果沒有請回去復習一維的處理方法。
令b[i,j]=a[i,j]*i? c[i,j]=a[i,j]*j ?d[i,j]=a[i,j]*i*j
維護a,b,c,d一共四個二維樹狀數組,問題得到解決。
tyvj p1716就是實現這兩個功能的裸題,下面給出完整代碼。
?
wy18899
沒原文鏈接,只能寫個原創,但是不是我寫的
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的树状数组维护区间和的模型及其拓广的简单总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SPI、I2C、UART 三种串行总线对
- 下一篇: leetcode415. 字符串相加