【前言】
树状数组可以处理单点修改,区间求和的操作,这里就不再赘述了。现在,我们要考虑用树状数组实现区间加,区间求和的操作。由于树状数组常数较小,代码简短,可以在一些场合代替线段树。
【实现】
首先明确一下,query(c,i)表示求c数组的前缀和。
定义要维护的序列为a[],对a[]进行一次差分,得到c[]。
即c[i]=a[i]-a[i-1],显然c[1]+c[2]+……+c[i]=a[i]
如何维护c[]?
当对区间[l,r]进行加w的操作时,c[l]+=w;c[r+1]-=w;
这样就成功维护了c[]。事实上,此时我们已经可以实现区间加,单点求和的操作了。
接下来考虑区间求和
a[1]+a[2]+……+a[i]=
(c[1])+(c[1]+c[2])+……+(c[1]+c[2]+……+c[i])=
c[1]*i+c[2]*(i-1)+……+c[i]=
(c[1]+c[2]+……+c[i])*i-(c[1]*0+c[2]*1+……+c[i]*(i-1))
我们就可以定义cc[i]=c[i]*(i-1)
答案就是query(c,i)*n-query(cc,i)
如何维护cc[]?
当对区间[l,r]进行加w的操作时,cc[l]+=w*(l-1);cc[r+1]+=-w*r
这样就维护了cc[],也就成功解决了所有问题。
贴上代码:
#include<cstdio>
#define LL long long
#define lowbit(x) ((x)&-(x))
const int maxn=200005;
int n,q;
LL c[maxn],cc[maxn];
inline int red(){
int tot=0,f=1;char ch=getchar();
while (ch<'0'||'9'<ch) {if (ch=='-') f=-f;ch=getchar();}
while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=getchar();
return tot*f;
}
void ist(LL *tre,int x,LL w){
for (int i=x;i<=n;i+=lowbit(i)) tre[i]+=w;
}
LL qry(LL *tre,int x){
LL res=0;
for (int i=x;i;i-=lowbit(i)) res+=tre[i];
return res;
}
void add(int l,int r,LL w){
ist(c,l,w);ist(c,r+1,-w);ist(cc,l,w*(l-1)),ist(cc,r+1,-w*r);
}
LL getsum(int x){return qry(c,x)*x-qry(cc,x);}
int main(){
n=red();
for (int i=1;i<=n;i++){
LL x=red();
add(i,i,x);
}
q=red();
while (q--)
if (red()==1){
int l=red(),r=red();LL w=red();
add(l,r,w);
}else{
int l=red(),r=red();
printf("%lld\n",getsum(r)-getsum(l-1));
}
return 0;
}