【树状数组】【模板3】区间修改,区间查询

本文介绍如何利用树状数组处理区间修改和区间查询的问题,通过差分数组和数学推导,详细解释了如何存储和计算区间值,以解决大规模数据的高效求解问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of op
eration is to add some given number to each number in a given interval. The other is to ask for the 
sum of numbers in a given interval.
1.给[a ,b]整体上加上一个常数c。 
2.查询[a ,b]区间的和。 

Input

The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000. 
The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000. 
Each of the next Q lines represents an operation. 
"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000. 
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab. 

Output

You need to answer all Q commands in order. One answer in a line. The sums may exceed the range of 32-bit integers

Sample Input

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

Sample Output

4
55
9
15

分析

利用模板2里的差分数组,我们可以快速地修改区间。

那么在使用差分数组的情况下如何快速查询区间值呢?

我们来推导一个式子

首先,查询区间值其实就是查询区间内的每一个元素,而在我们的差分数组中每个元素等于对应的前缀和

所以我们需要求的区间值就变成了差分数组前若干个前缀和之和

写出来就是这样

a[1] + (a[1] + a[2]) + (a[1] + a[2] + a[3]) + ...

我们进行一下变形(这步要看懂)

k * (a[1] + a[2] + ...) - (0 * a[1] + 1 * a[2] + ...)

通过观察我们可以发现后面那一堆其实都是(i - 1) *  a[ i ]

写成求和符号

k * \sum_{i = 1}^{k}a[i] - \sum_{i = 1}^{k}(i - 1) * a[i]

那么这道题就很清晰了

我们只需要再使用一个树状数组存下(i - 1) *  a[ i ]就可以快速计算后面那一坨

\sum_{j = l}^ {r} \sum_{i = l} ^ {j}tree[i]

然后就可以算出区间值了

代码

#include<iostream>
#include<cstdio>
using namespace std;
 
long long n, q;
long long a[100010];
long long tree1[100010];
long long tree2[100010];
 
inline long long read() {
    long long x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9') {x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();}
    return x * f;
}
 
long long ask(long long *tree, long long x) {
    long long sum = 0;
    for (; x; x -= (x & -x)) sum += tree[x];
    return sum;
}
 
void add(long long *tree, long long x, long long y) {
    for (; x <= n; x += (x & -x)) tree[x] += y;
}
 
 
int main() {
    n = read(), q = read();
    for (long long i = 1; i <= n; i++) a[i] = a[i - 1] + read();
    while (q--) {
        char c;
        cin >> c;
        if (c == 'C') {
            long long l = read(), r = read(), x = read();
            add(tree1, l, x);
            add(tree1, r + 1, -x);
            add(tree2, l, l * x);
            add(tree2, r + 1, -(r + 1) * x);
        }
        else {
            long long l = read(), r = read(), ans = 0;
            ans = (a[r] + (r + 1) * ask(tree1, r) - ask(tree2, r)) - (a[l - 1] + l * ask(tree1, l - 1) - ask(tree2, l - 1));
            printf("%lld\n", ans);
        }
    }
}
 

 

### 树状数组区间修改实现 树状数组是一种高效的数据结构,通常用于处理单点修改区间查询问题。然而,在某些情况下,也需要支持 **区间修改** 和 **区间查询** 的操作。为了实现这一目标,可以通过引入差分数组的思想来完成。 以下是基于差分数组思想的树状数组区间修改查询的具体实现: #### 差分数组的概念 差分数组的核心思想是通过对原数组进行预处理,使得对某个区间的加法操作转化为两个单点的操作。对于一个长度为 \( n \) 的数组 \( a[] \),定义其对应的差分数组 \( b[] \) 如下: \[ b[i] = a[i] - a[i-1],\quad (i>0)\] 如果需要将区间 \([l, r]\) 上的所有元素加上 \( x \),则只需执行以下两步操作: 1. 将位置 \( l \) 处的值增加 \( x \): \( b[l] += x \)[^3] 2. 将位置 \( r+1 \) 处的值减少 \( x \): \( b[r+1] -= x \) 最终通过计算前缀和即可恢复原始数组的状态。 --- #### 区间修改查询的代码模板 下面是一个完整的 C++ 实现,展示了如何利用树状数组支持区间修改区间查询的功能。 ```cpp #include <bits/stdc++.h> using namespace std; // lowbit 函数 inline int lowbit(int x) { return x & (-x); } // 树状数组类 class BIT { public: vector<int> tree; int size; // 初始化树状数组 BIT(int n) : size(n), tree(n + 2, 0) {} // 单点更新函数 void update(int idx, int delta) { while (idx <= size) { tree[idx] += delta; idx += lowbit(idx); } } // 前缀和查询函数 int query_prefix(int idx) const { int res = 0; while (idx > 0) { res += tree[idx]; idx -= lowbit(idx); } return res; } // 查询区间 [left, right] 的和 int query_range(int left, int right) const { return query_prefix(right) - query_prefix(left - 1); } }; // 主程序部分 void solve() { int n, q; cin >> n >> q; // 数组大小和询问数量 // 构造两个树状数组分别存储差分数组及其辅助信息 BIT bit_add(n); // 存储 add 操作的结果 BIT bit_subtract(n); // 辅助树状数组,用于修正偏移量 while (q--) { char type; cin >> type; if (type == &#39;U&#39;) { // 更新操作 U l r x 表示给 [l, r] 加上 x int l, r, x; cin >> l >> r >> x; bit_add.update(l, x); // 对应于差分数组中的 add(l, x) bit_add.update(r + 1, -x); // 对应于差分数组中的 add(r+1, -x) bit_subtract.update(l, x * (l - 1)); // 记录偏移量调整项 bit_subtract.update(r + 1, -x * r); // 记录偏移量调整项 } else if (type == &#39;Q&#39;) { // 查询操作 Q k 表示求第 k 位的数值 int k; cin >> k; long long result = bit_add.query_prefix(k) * k - bit_subtract.query_prefix(k); cout << result << "\n"; } } } ``` --- #### 关键点解析 1. **差分数组的作用** 使用差分数组可以将区间修改转换成两次单点修改,从而降低时间复杂度至 \( O(\log n) \)。 2. **双树状数组的设计** 由于直接维护差分数组无法快速获取任意一点的实际值,因此设计了第二个树状数组 `bit_subtract` 来记录偏移量调整项[^4]。 3. **时间复杂度分析** - 修改操作的时间复杂度为 \( O(\log n) \) - 查询操作的时间复杂度同样为 \( O(\log n) \) --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值