【codevs1082】线段树练习3——树状数组(改段求段)
经典的改段求段模型,题目都告诉你要用线段树做了,当然这种操作用树状数组来写就够了。
这里需要用到两个辅组数组X和Y,每次操作时,相当于:
X[l]+=val;X[r+1]-=val;Y[l]+=-1*val*(l-1);Y[r+1]+=r*val;
以上修改代价是O(logn)的。
对于求1~l的和,答案就是:
get_sum(l)=X.sum(l)*l+Y.sum(l)
那么每次查询的答案就是:
get_sum(r)-get_sum(l-1)
解释:
首先X.sum(l)与改段求点型类似,为l这个点增加的值,也就是说X.sum(l)*l表示如果把前l个数增加的值都看成和l相同的话答案是多少。
但是前l个点增加的值显然不一定和l相同啊,咋办咧?
这时候就要看式子的后半部分Y.sum(l)了:
Y.sum(l)相当于求出前l个数增加的部分有多少是被算多的了,因为之前已经先*-1了所以直接加上就好。
查询代价O(logn),和线段树相同。
特别的,l-1可能为0,需要特判。
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<queue> 5 #define lowbit(x) x&-x 6 typedef long long LL; 7 const int N=2e5+10; 8 LL n,q,a[N],X[N],Y[N]; 9 int read(){ 10 int ans=0,f=1;char c=getchar(); 11 while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} 12 while(c>='0'&&c<='9'){ans=ans*10+c-48;c=getchar();} 13 return ans*f; 14 } 15 LL sum(int r,LL c[]){ 16 LL ans=0; 17 if(!r)ans=c[0]; 18 for(int i=r;i;i-=lowbit(i))ans+=c[i]; 19 return ans; 20 } 21 void add(int r,int v,LL c[]){ 22 if(!r){c[0]+=v;return;} 23 for(;r<=n;r+=lowbit(r))c[r]+=v; 24 } 25 LL get_sum(int x){return sum(x,X)*x+sum(x,Y);} 26 int main(){ 27 n=read(); 28 for(int i=1;i<=n;i++)a[i]=a[i-1]+read(); 29 q=read(); 30 while(q--){ 31 int op=read(),l=read(),r=read(),v; 32 if(!(op-1)){ 33 v=read(); 34 add(l,v,X);add(r+1,-v,X);add(l,-1*v*(l-1),Y);add(r+1,r*v,Y); 35 } 36 else printf("%lld\n",get_sum(r)-get_sum(l-1)+a[r]-a[l-1]); 37 } 38 return 0; 39 }