树状数组区间修改(2026.3.22)
树状数组区间修改(2026.3.22)
本文章将从一维到二维讲解树状数组如何实现区间修改 + 区间查询。
一维树状数组本身原生只支持“单点修改 + 区间查询”。
要实现区间修改 + 区间查询,必须引入 差分(Difference Array) 的思想。
一维:区间修改 + 区间查询
如果你需要给区间 加上 ,并且查询区间 的和,需要维护两个树状数组。
核心推导
我们要查询原数组 的前缀和 。
已知 ( 是差分数组)。
代入展开 :
结论:
要计算 ,我们需要维护两个前缀和:
因此,我们需要两个树状数组:
bit1:维护bit2:维护
操作逻辑
区间修改 加 :
- 对 加 ,对 减 。
- 同时更新
bit1和bit2:bit1在 加 ,在 减 。bit2在 加 ,在 减 。
前缀和查询 :
- 公式:
query1(x) * (x + 1) - query2(x)
- 公式:
区间和查询 :
S[r] - S[l-1]
完整代码模板
// https://www.luogu.com.cn/problem/P3372
#include <bits/stdc++.h>
#define int long long
class BIT {
public:
BIT(std::vector<int>& a) : tr(a), n(a.size()) {
for (int i = 1; i < n; i++) {
int j = i + (i & -i);
if (j < n) tr[j] += tr[i];
}
}
void update(int x, const int v) {
for (; x < n; x += x & -x) {
tr[x] += v;
}
}
[[nodiscard]] int query(int x) const {
int res = 0;
for (; x; x -= x & -x) {
res += tr[x];
}
return res;
}
[[nodiscard]] int query(const int l, const int r) const {
return query(r) - query(l - 1);
}
private:
std::vector<int> tr;
size_t n;
};
signed main() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++) std::cin >> a[i];
for (int i = n; i >= 1; i--) a[i] -= a[i - 1];
BIT tr1(a);
for (int i = 1; i <= n; i++) a[i] = a[i] * i;
BIT tr2(a);
while (m--) {
int op;
std::cin >> op;
if (op == 1) {
int x, y, k;
std::cin >> x >> y >> k;
tr1.update(x, k), tr1.update(y + 1, -k);
tr2.update(x, k * x), tr2.update(y + 1, -k * (y + 1));
} else {
int x, y;
std::cin >> x >> y;
std::cout << (y + 1) * tr1.query(y) - tr2.query(y) - (x * tr1.query(x - 1) - tr2.query(x - 1)) << "\n";
}
}
return 0;
}二维:区间修改 + 区间查询
下面是二维树状数组实现区间修改、区间查询的完整数学推导过程。
1. 基础定义
设原二维数组为 。
我们要支持的操作是:给矩形区域 到 的所有元素加上 。
为了利用树状数组,我们引入二维差分数组 。
差分定义:
(即:原数组的值等于差分数组的二维前缀和)
差分更新逻辑:
若要将 中矩形 加 ,只需对 进行四次单点修改:
2. 目标推导
我们的目标是求原数组 的二维前缀和 :
将 用差分 替换:
第一步:交换求和顺序(统计贡献次数)
现在的式子是四层循环。我们需要思考:每一个差分项 在这个大求和中被计算了多少次?
- 会被计入 的条件是: 且 。
- 在外层求和 中,满足 的 有 个(即 )。
- 同理,满足 的 有 个。
因此, 总共被累加了 次。
公式重写为:
(注:为了代码习惯,下文将求和变量 换回 )
第二步:展开多项式
将系数 展开:
将这个展开式代回 的求和公式中:
第三步:拆分求和项
利用求和的线性性质,将上式拆分为四部分。注意 和 对于内部的 求和来说是常数,可以提取到求和符号外面。
3. 最终结论与数据结构设计
观察上面的四项,每一部分都是一个二维前缀和的形式。我们需要维护四个不同的二维树状数组(BIT),分别存储以下内容:
| 序号 | 树状数组名称 | 存储内容 (Value at ) | 对应求和项 |
|---|---|---|---|
| 1 | bit0 | ||
| 2 | bit1 | ||
| 3 | bit2 | ||
| 4 | bit3 |
(注:代码实现中 bit1 和 bit2 的顺序可以互换,只要公式对应即可。上文代码中 bit1 对应乘 ,bit2 对应乘 )
最终计算公式
设 为第 个树状数组的前缀和查询结果,则:
其中:
- (存的是 )
- (存的是 )
- (存的是 )
区间修改时的更新操作
当我们要对差分数组 加上 时,需要同步更新这四个树状数组:
bit0.update(x, y, v)bit1.update(x, y, v * x)bit2.update(x, y, v * y)bit3.update(x, y, v * x * y)
4. 完整代码模板
// https://www.luogu.com.cn/problem/P4514
#include <bits/stdc++.h>
#define int long long
class BIT {
public:
// 本题初始数组就是全0,所以不需要在构造函数中建树
explicit BIT(const std::vector<std::vector<int>>& a) : tr(a), n(a.size()), m(a[0].size()) { }
void update(int x, int y, const int v) {
for (; x < n; x += x & -x) {
for (int j = y; j < m; j += j & -j) {
tr[x][j] += v;
}
}
}
[[nodiscard]] int query(int x, int y) const {
int res = 0;
for (; x; x -= x & -x) {
for (int j = y; j; j -= j & -j) {
res += tr[x][j];
}
}
return res;
}
private:
std::vector<std::vector<int>> tr;
size_t n;
size_t m;
};
signed main() {
char cha;
int n, m;
std::cin >> cha >> n >> m;
const std::vector x(n + 1, std::vector<int>(m + 1));
BIT bit0(x), bit1(x), bit2(x), bit3(x);
auto sum = [&](const int x_, const int y) -> int {
return (x_ + 1) * (y + 1) * bit0.query(x_, y) - (x_ + 1) * bit2.query(x_, y) -
(y + 1) * bit1.query(x_, y) + bit3.query(x_, y);
};
while (std::cin >> cha) {
if (cha == 'L') {
int a, b, c, d, delta;
std::cin >> a >> b >> c >> d >> delta;
bit0.update(a, b, delta);
bit0.update(a, d + 1, -delta);
bit0.update(c + 1, b, -delta);
bit0.update(c + 1, d + 1, delta);
bit1.update(a, b, a * delta);
bit1.update(a, d + 1, -a * delta);
bit1.update(c + 1, b, -(c + 1) * delta);
bit1.update(c + 1, d + 1, (c + 1) * delta);
bit2.update(a, b, b * delta);
bit2.update(a, d + 1, -(d + 1) * delta);
bit2.update(c + 1, b, -b * delta);
bit2.update(c + 1, d + 1, (d + 1) * delta);
bit3.update(a, b, a * b * delta);
bit3.update(a, d + 1, -a * (d + 1) * delta);
bit3.update(c + 1, b, -(c + 1) * b * delta);
bit3.update(c + 1, d + 1, (c + 1) * (d + 1) * delta);
} else {
int a, b, c, d;
std::cin >> a >> b >> c >> d;
std::cout << sum(c, d) - sum(a - 1, d) - sum(c, b - 1) + sum(a - 1, b - 1) << "\n";
}
}
return 0;
}最后更新于:2026-03-22