【树状数组区间修改区间求和】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 }
View Code

【原理】

原理是用了差分数组,转载自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     }
View Code

然后进行查询求和

 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 }
View Code

进行单点查询时,只需两个参数均传入该点。

在进行区间更新的时候,神牛市通过两次维护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 }
View Code

 

posted @ 2017-08-26 21:12  shulin15  阅读(331)  评论(0编辑  收藏  举报