樹狀陣列-區間查詢+區間修改
聽說樹狀陣列可以支援區間加??今天特地跑去這裡學習了一下,%%%%%%%%%%%%%,下面結合我的理解再講一講
有關樹狀陣列的基礎知識我就不贅述了,想必大家都明白,如果不清楚可以自己百度,畢竟這不是蒟蒻三言兩語就可以講通的
那現在假設你已經會了樹狀陣列的 “ 單點修改,區間詢問 ” ,我們就來講一講升級版的 “ 區間修改,區間詢問 ”
【寫在前面】
區間修改:
我們讓 sigma (r ,j )表示 r 陣列的前 j 項和,即sigma (r ,j )=r [ 1 ] + r [ 2 ] +r [ 3 ]+……+r [ j ]
用 a 陣列表示原序列,其差分序列為 c 陣列,即 c [ i ]= a [ i ] - a [ i - 1 ]
顯然,a [ j ] = sigma(c ,j )
然後我們發現若要對序列中的 i ~ j 個數進行操作(比如都加上一個 v ),其實就是 c [ i ] + = v , c [ j + 1 ] - = v,因為其他在 i ~ j 中間的數由於都加了一個 v ,其對應的 c 陣列不會改變
【寫在中間】
區間查詢:
首先使用字首和相減這不用多說,那我們就來考慮如何求 1 ~ q 這個區間內各個數的和
ans = a[1] + a[2] + a[3] +……+ a[q-1] + a[q]
=sigma(c,1) + sigma(c,2) + sigma(c,3) +…… + sigma(c, q-1 ) + sigma(c, q )
=c[1] + ( c[1] + c[2] ) + ( c[1] + c[2] + c[3] ) + …… + (c[1] + c[2]+……+ c[q-1] )+ ( c[1] + c[2]+……+ c[q] )
= q*c[1] + (q-1)*c[2] + (q-2)*c[3] +…… + c[q]
= q* (c[1]+c[2]+...+c[q]) - (0*c[1]+1*c[2]+...+(q-1)*c[q])
A B
A部分很好辦,就是一般的樹狀陣列求字首和
而對於B部分,我們可以將其看作一個新的陣列c2[i]=(i-1)*c[i],然後照例維護一下即可
每當修改c的時候,就同步修改一下c2,這樣複雜度就不會改變
所以 ans=q*sigma(c,q)-sigma(c2,q)
【寫在最後】
其實任何一道區間加和區間詢問的題都可以交上去試一下
具體程式碼:
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define N 100009
using namespace std;
int lowbits(int x){return x&(-x);}
int n,m;
ll c1[N],c2[N],a[N];
void add(ll *t,ll k,int i){
while(i<=n){ t[i]+=k; i+=lowbits(i); }
}
ll querysum(ll *t,int x){
ll res=0;
while(x){ res+=t[x];x-=lowbits(x); }
return res;
}
int main(){
scanf("%d%d",&n,&m);
int i,j,k;
for(i=1;i<=n;++i){
scanf("%lld",&a[i]);
add(c1,a[i]-a[i-1],i);
add(c2,(i-1)*(a[i]-a[i-1]),i);///////中間不可以寫成(i-1)*c1[i],因為這裡的c1[i]不是我們定義的那個,它是樹狀數組裡的,包含了他前面的值的
}
for(i=1;i<=m;++i){
int ty,x,y;
ll z;
scanf("%d%d%d",&ty,&x,&y);
if(ty==1){
scanf("%lld",&z);
add(c1,z,x);add(c1,-z,y+1);
add(c2,(x-1)*z,x);////和上面同理
add(c2,-y*z,y+1);
}
else{
ll ans;
ans=y*querysum(c1,y)-querysum(c2,y);
ans-=(x-1)*querysum(c1,x-1)-querysum(c2,x-1);
printf("%lld\n",ans);
}
}
return 0;
}
那這個程式碼和線段樹的一比,你就知道為什麼我要學了,嘿嘿