【树状数组区间修改区间求和】codevs 1082 线段树练习 3
http://codevs.cn/problem/1082/
【AC】
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int maxn=2e5+2; 5 int n; 6 ll a[maxn]; 7 ll c1[maxn]; 8 ll c2[maxn]; 9 int lowbit(int x) 10 { 11 return x&-x; 12 } 13 void add(ll *c,int k,ll val) 14 { 15 while(k<=n){ 16 c[k]+=val; 17 k+=lowbit(k); 18 } 19 } 20 ll query(ll *c,int k) 21 { 22 ll ans=0; 23 while(k) 24 { 25 ans+=c[k]; 26 k-=lowbit(k); 27 } 28 return ans; 29 } 30 ll solve(int x) 31 { 32 ll ans=0; 33 ans+=x*query(c1,x); 34 ans-=query(c2,x); 35 return ans; 36 } 37 ll solve(int x,int y) 38 { 39 return solve(y)-solve(x-1); 40 } 41 int main() 42 { 43 while(~scanf("%d",&n)) 44 { 45 a[0]=0; 46 for(int i=1;i<=n;i++) 47 { 48 scanf("%I64d",&a[i]); 49 // cout<<a[i]<<endl; 50 add(c1,i,a[i]-a[i-1]); 51 add(c2,i,(i-1)*(a[i]-a[i-1])); 52 } 53 int q; 54 scanf("%d",&q); 55 int tp; 56 while(q--) 57 { 58 scanf("%d",&tp); 59 if(tp==1) 60 { 61 int x,y;ll val; 62 scanf("%d%d%I64d",&x,&y,&val); 63 add(c1,x,val); 64 add(c1,y+1,-val); 65 add(c2,x,1ll*(x-1)*val); 66 add(c2,y+1,-1ll*y*val); 67 } 68 else 69 { 70 int x,y; 71 scanf("%d%d",&x,&y); 72 ll ans=solve(x,y); 73 printf("%I64d\n",ans); 74 } 75 } 76 } 77 return 0; 78 }
【原理】
原理是用了差分数组,转载自http://www.cnblogs.com/boceng/p/7222751.html
树状数组时间复杂度为O(MlogN), 实际用的时候优于线段树,且写得少。
神牛是引入了差分数组,要维护的差分数组ks[i] = a[i] - a[i-1]; 可以容易得到a[i] = ks[1] + ks[2] + ... + ks[i]; 即前i项和,为方便记为sigma(ks, i),已经可以看到树状数组的影子了,所以求区间和随之得到
a[1] + a[2] + .. + a[n] = sigma(ks, 1) + sigma(ks, 2) + ... + sigma(ks, n);
= n*ks[1] + (n-1)*ks[2] + ... + 2*ks[n-1] + 1*ks[n];
= n*(ks[1] + ks[2] +...+ ks[n]) - (0*ks[1] + 1*ks[2] + ... + (n-1)*ks[n]);
所以可以得到 sum[n] =n * sigma(ks, n) - (0*ks[1] + 1*ks[2] + ... + (n-1)*ks[n]);
令jk[i] = (i-1) * ks[i];
则 sum[n] = n * sigma(ks, n) - sigma(jk, n);
之后便是构造两个树状数组;
1 int lowbit(int k){ 2 return k & -k; 3 } 4 void add(int n, int *c, int k, int va){ 5 while(k <= n){ 6 c[k] += va; 7 k += lowbit(k); 8 } 9 } 10 11 //------------------------------------- 12 13 for(i = 1; i <= n; ++i){ 14 add(n, c1, i, jk[i]-jk[i-1]); 15 add(n, c2, i, (i-1)*(jk[i]-jk[i-1])); 16 }
然后进行查询求和
1 int sigma(int *c, int k){ 2 int sum = 0; 3 while(k){ 4 sum += c[k]; 5 k -= lowbit(k); 6 } 7 return sum; 8 } 9 int getSum(int s, int t){ 10 return (t*sigma(c1, t)-sigma(c2, t)) - ((s-1)*sigma(c1, s-1)-sigma(c2, s-1)); 11 }
进行单点查询时,只需两个参数均传入该点。
在进行区间更新的时候,神牛市通过两次维护c1,两次c2得到的,但本人推测了几种情况,都不能很好的解释这么做的原因,
1 void update(int s, int t, int va){ 2 add(c1, s, va); 3 add(c1, t+1, -va); 4 add(c2, s, va*(s-1)); 5 add(c2, t+1, -va*t); 6 }