算法竞赛进阶指南---0x42(树状数组)一个简单的整数问题2

题面

在这里插入图片描述

输入样例

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

输出样例

4
55
9
15

题解

  1. 这道题涉及区间修改和区间查询,当然可以用线段树来做,但是杀鸡焉用宰牛刀,可以用树状数组解决的问题大可不必用线段树解决,如果对树状数组不了解的可以先看看这里,还是用一个简单的整数问题 那道题的思想,差分数组 (树状数组支持O(logn)单点修改,区间查询)
  1. “C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d ,我们可以将其转化为差分数组,对于一段区间,我们只需要修改差分数组中的两个端点 b[l]+=c,b[r+1]-=c; 这样就可以实现区间修改了
  1. “Q l r”,表示询问 数列中第 l~r 个数的和。我们将原数组转化为差分数组以后,就无法进行区间查询了,因为差分数组求的前缀和是原数组的一个值,而不是原数组的区间和,那么怎样求原数组的区间和呢?
    在这里插入图片描述

4.经过上边公式的推导,我们发现,只需要维护两个树状数组,一个是a[i] - a[i - 1] ,一个是i * (a[i] - a[i - 1]) ,这样就可以求出元素组的[1,x]的区间和,那么利用前缀和思想就可以求出原数组的任意区间,这样就完全转化为了树状数组的基本操作了

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>


using namespace std;
typedef long long ll;
const int N = 1e5 + 10;

ll n, m;
ll a[N];  //原数组
ll tr1[N];  //b[i]的前缀和数组
ll tr2[N];   //i*b[i]的前缀和数组

ll lowbit(ll x) {
    
    
    return x & -x;
}

void add(ll tr[], ll x, ll c) {
    
    
    for (ll i = x; i <= n; i += lowbit(i)) {
    
    
        tr[i] += c;
    }
}

ll sum(ll tr[], ll x) {
    
    
    ll res = 0;
    for (ll i = x; i; i -= lowbit(i)) {
    
    
        res += tr[i];
    }
    return res;
}

ll prefix_sum(ll x) {
    
    
    return sum(tr1, x) * (x + 1) - sum(tr2, x);
}

int main() {
    
    

    cin >> n >> m;
    for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    for (int i = 1; i <= n; i++) add(tr1, i, a[i] - a[i - 1]);
    for (int i = 1; i <= n; i++) add(tr2, i, i * (a[i] - a[i - 1]));

    while (m--) {
    
    
        string op;
        int l, r, d;
        cin >> op >> l >> r;
        if (op == "Q") {
    
    
            cout << prefix_sum(r) - prefix_sum(l - 1) << endl;
        } else {
    
    
            cin >> d;
            add(tr1, l, d), add(tr1, r + 1, -d);
            add(tr2, l, l * d), add(tr2, r + 1, -(r + 1) * d);
        }
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44791484/article/details/113826577