听说树状数组可以支持区间加??今天特地跑去这里学习了一下,%%%%%%%%%%%%%,下面结合我的理解再讲一讲
有关树状数组的基础知识我就不赘述了,想必大家都明白,如果不清楚可以自己百度,毕竟这不是蒟蒻三言两语就可以讲通的
那现在假设你已经会了树状数组的 “ 单点修改,区间询问 ” ,我们就来讲一讲升级版的 “ 区间修改,区间询问 ”
【写在前面】
区间修改:
我们让 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)
【写在最后】
例题:洛谷3372
其实任何一道区间加和区间询问的题都可以交上去试一下
具体代码:
#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;
}
那这个代码和线段树的一比,你就知道为什么我要学了,嘿嘿