树状数组模板(改点求段 / 该段求点 / 改段求段)

1. 改点求段(单点更新, 区间求和)

代码:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 const int MAXN = 1e5 + 10;
 5 int tree[MAXN], n;
 6 
 7 int lowbit(int x){//返回 pow(2, k),其中k为末尾0的个数, 即返回最低位1的值
 8     return x & -x;
 9 }
10 
11 void add(int x, int d){//将d累加到tree数组对应位置
12     while(x <= n){
13         tree[x] += d;
14         x += lowbit(x);
15     }
16 }
17 
18 int sum(int x){//向上求和, 返回[1, x]所有元素的和
19     int ans = 0;
20     while(x > 0){
21         ans += tree[x];
22         x -= lowbit(x);
23     }
24     return ans;
25 }
26 
27 int main(void){
28     int x;
29     cin >> n;
30     for(int i = 1; i <= n; i++){
31         cin >> x;
32         add(i, x);
33     }
34     for(int i = 1; i <= n; i++){
35         cout << sum(i) << endl;
36     }
37     return 0;
38 }
View Code

 

2. 改点求段(区间更新, 单点求值)

 用一个数组 d 存储目标数组 a 中相邻元素的差值, 即 i > 1 时, d[i] = a[i] - a[i - 1] ; i == 1 时, d[i] = a[i] .

那么有 a[i] = d[1] + ... + d[i] .若要将 a 数组区间 [l, r] 的元素都加上 key, 显然只需令 d[l] += key, d[r + 1] -= key 即可.

显然只要用树状数组维护一下 d 数组即可.

代码:

 1 #include <iostream>
 2 #include <stdio.h>
 3 using namespace std;
 4 
 5 const int MAXN = 1e5 + 10;
 6 int  a[MAXN], tree[MAXN], d[MAXN], n;
 7 
 8 int lowbit(int x){
 9     return x & -x;
10 }
11 
12 void add(int x, int ad){
13     while(x <= n){
14         tree[x] += ad;
15         x += lowbit(x);
16     }
17 }
18 
19 int sum(int x){//sum(x)为a[x]的值
20     int ans = 0;
21     while(x > 0){
22         ans += tree[x];
23         x -= lowbit(x);
24     }
25     return ans;
26 }
27 
28 int main(void){
29     int x, q, l, r;
30     cin >> n;
31     for(int i = 1; i <= n; i++){
32         scanf("%d", &a[i]);
33         if(i == 1) d[i] = a[i];
34         else d[i] = a[i] - a[i - 1];
35     }
36     for(int i = 1; i <= n; i++){
37         add(i, d[i]);
38     }
39     cin >> q;
40     while(q--){
41         cin >> l >> r >> x;//将区间[l, r]的元素都加上x
42         add(l, x);
43         add(r + 1, -x);
44         for(int i = 1; i <= n; i++){
45             cout << sum(i) << " ";
46         }
47         cout << endl;
48     }
49 }
View Code

 

3. 改段求段

 与 2 中类似, 先开一个差分数组 d

那么有:

a1 + a2 + ... + an

= d1 + (d1 + d2) + ... + (d1 + d2 + ... + dn)

= n * d1 + (n - 1) * d2 + ... + dn

= n * (d1 + d2 + ... + dn) - (0 * d1 + 1 * d2 + ... (n - 1) * dn)

再令 c[i] = ( i - 1) * di

那么原式可化简为:

 n * (d1 + d2 + ... + dn) - (c1 + c2 + ... cn)

显然对于 d 和 c 数组求和可以用树状数组解决. 而由 2 可知 a 数组区间修改则只需要对 d 和 c 数组对应做单点修改即可.

 

例题: http://codevs.cn/problem/1082/

题意: 中文题诶~

思路: 树状数组区间更新区间求和模板

代码:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #define ll long long
 4 using namespace std;
 5 
 6 const int MAXN = 2e5 + 10;
 7 ll a[MAXN], d[MAXN], c[MAXN];//d[i]=a[i]-a[i-1], c[i]=(i-1)*d[i]
 8 int n;
 9 
10 int lowbit(int x){
11     return x & -x;
12 }
13 
14 void add(ll *tree, int x, ll ad){
15     while(x <= n){
16         tree[x] += ad;
17         x += lowbit(x);
18     }
19 }
20 
21 ll sum(ll *tree, int x){
22     ll ans = 0;
23     while(x > 0){
24         ans += tree[x];
25         x -= lowbit(x);
26     }
27     return ans;
28 }
29 
30 int main(void){
31     ll ad;
32     int q, op, l, r;
33     scanf("%d", &n);
34     for(int i = 1; i <= n; i++){
35         scanf("%lld", &a[i]);
36         add(d, i, a[i] - a[i - 1]);
37         add(c, i, (i - 1) * (a[i] - a[i - 1]));
38     }
39     scanf("%d", &q);
40     while(q--){
41         scanf("%d", &op);
42         if(op == 1){
43             scanf("%d%d%lld", &l, &r, &ad);
44             add(d, l, ad);
45             add(d, r + 1, -ad);
46             add(c, l, (l - 1) * ad);
47             add(c, r + 1, -ad * r);
48         }else{
49             scanf("%d%d", &l, &r);
50             ll sum1 = (l - 1) * sum(d, l - 1) - sum(c, l - 1);
51             ll sum2 = r * sum(d, r) - sum(c, r);
52             printf("%lld\n", sum2 - sum1);
53         }
54     }
55     return 0;
56 }
View Code

 

posted @ 2017-07-25 22:10  geloutingyu  阅读(525)  评论(3编辑  收藏  举报