树状数组模板(改点求段 / 该段求点 / 改段求段)
Posted ygeloutingyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组模板(改点求段 / 该段求点 / 改段求段)相关的知识,希望对你有一定的参考价值。
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 }
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 }
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 }
以上是关于树状数组模板(改点求段 / 该段求点 / 改段求段)的主要内容,如果未能解决你的问题,请参考以下文章