众所周知:

  • 线段树的代码长,常数大;
  • 树状数组的代码短,常数小,甚至可以通过 106 量级的数据。

所以,能不能实现一个可以区间修改、区间查询的树状数组呢?


由于涉及区间操作,考虑差分数组 {dn}

区间修改

对于原数组 [l, r] 区间每个数加 w

可以转化为两次单点修改,即 l 单点处加 +wr + 1 单点处加 w

区间查询

对于原数组 [l, r] 区间求和。

显然 可以差分为两个 [1, u] 的前缀求和。

观察每一个 ,可以发现

所以 d1 的贡献为 ud2 的贡献为 u − 1d3 的贡献为 u − 2,……,du 的贡献为 1

故可得 dk 的贡献为 u − j + 1

发现 u + 1 的值是固定的,可以提取出来:

因此同时使用两个树状数组维护 {dn}{n × dn} 即可,该技巧即为超级树状数组。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef long long lint;
lint sum(int p, lint *t) { // 查询 t 中 [1,p] 之和
lint res = 0;
for (int i = p; i; i -= lowbit(i))
res += t[i];
return res;
}

lint ask(int p) {
return sum(p, d) * (p + 1) - sum(p, f);
}

lint query(int l, int r) {
return ask(r) - ask(l - 1);
}

void add(int p, lint x, lint *t) {
for (int i = p; i <= n; i += lowbit(i)) t[i] += x;
}

void update(int l, int r, lint x) {
add(l, x, d), add(r + 1, -x, d);
add(l, x * l, f);
add(r + 1, -x * (r + 1), f);
}