前言
树状数组是解决动态区间和问题的数据结构
性质
树状数组每一位置并不是只存储其本身的值。例如C8存储的是A1-8整个区间的值,C7存储的是A7的值,C6存储的是A5、A6的值。这里有一个性质,设某一节点为x,那么这个节点管辖的区域为2^k(k表示为x二进制末尾0的个数。比如C8,为1000,末尾有3个0,那么它存储2 ^ 3位置的值;C6,为110,末尾有1个0,那么它存储2 ^ 1位置的值;C7,为111,末尾没有0,那么它存储2 ^ 0位置的值。
查询
树状数组一次查询的复杂度为O(logn)。他的原理是什么呢?进行二进制拆分。比如我们要查询13的前缀和,我们转化成二进制1101,有3个1,每次在最后一个1的位置减1,也就是就把它拆成1101,1100,1000。这三个数就是我们要相加求出的答案。C8+C12+C13=A(1——8)+A(9——12)+A(13),也就是13的前缀和
修改
我们要维护树状数组,显然不能只维护一个位置,而要把包含这个值的区间全部进行修改。如果我们要修改A6,那么我们要修改C6、C8、C16…。修改是在最后一个1的位置加1,也就是110、1000、10000.
lowbit()操作
查询是每次在最后一个1的位置减1,修改是在最后一个1的位置加1,那么我们如何得到最后一个1的位置呢?lowbit()操作–得到末尾最后一个1的值。我们要知道计算机存储负数是以补码的形式,也就是正数取反再加1.例如12,1100,-12,0011+1=0100,12&(-12)=0100=4
单点修改、单点查询
void add(int x,int k){
for(;x<=n;x+=x&-x)t[x]+=k;
}
int ask(int x){
int ans=0;
for(;x;x-=x&-x)ans+=t[x];
return ans;
}
//add(x,k);
//ask(x)-ask(x-1);
单点修改、区间查询
//add(x,k);
//ask(r)-ask(l-1)
区间修改、单点查询
利用一维差分
//add(l,d);add(r+1,-d);
//a[x]+ask(x)
区间修改、区间查询
∑ni = 1A[i] = ∑ni = 1 ∑ij = 1D[j];
则A[1]+A[2]+…+A[n]
= (D[1]) + (D[1]+D[2]) + … + (D[1]+D[2]+…+D[n])
= n*D[1] + (n-1)*D[2] +… +D[n]
= n * (D[1]+D[2]+…+D[n]) - (0D[1]+1D[2]+…+(n-1)*D[n])
所以上式可以变为∑ni = 1A[i] = n∑ni = 1D[i] - ∑ni = 1( D[i](i-1) );
如果你理解前面的都比较轻松的话,这里也就知道要干嘛了,维护两个数状数组,sum1[i] = D[i],sum2[i] = D[i]*(i-1);
#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll t1[maxn],t2[maxn],a[maxn];
int n,m;
void add(int x,ll k){
int a=x;
for(;x<=n;x+=x&-x) t1[x]+=k,t2[x]+=k*(a-1);
}
ll getsum(int x){
ll ans=0,a=x;
for(;x;x-=x&-x)ans+=a*t1[x]-t2[x];
return ans;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
add(i,a[i]-a[i-1]);
}
while(m--){
char c;ll x,y,k;
cin>>c;
if(c=='C'){
scanf("%lld%lld%lld",&x,&y,&k);
add(x,k);add(y+1,-k);
}
if(c=='Q'){
scanf("%lld%lld",&x,&y);
printf("%lld\n",getsum(y)-getsum(x-1));
}
}
return 0;
}