树状数组
树状数组是一种特殊的高效数据结构:时空复杂度和线段树类似,但是它的系数要小的多。树状数组是一个查询和修改复杂度都为
树状数组的推导
若正整数
它们的长度分别为
同时,我们发现如果区间以右端点
由此,区间
for (int i = x; i; i -= lowbit(i)) {
// 区间 [i - lowbit(i) + 1, i]
printf("[%d, %d]\n", i - lowbit(i) + 1, i);
}而树状数组就是基于上述思想的一种数据结构,其基本用途就是维护序列的前缀和。
设数组

由上图我们可以推出:
的最后一个元素必是 。 的子节点个数为 的位数。 - 除树根外,
的父节点为 。 - 树的深度为
,其中 为序列元素个数,当 不是 的整数次幂时,树状数组是一个森林结构。
两个基本操作
1. 查询前缀和
查询序列
int query(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}如要查询区间
2. 单点增加
单点增加即对序列
void add(int x, int y) {
for (int i = x; i <= N; i += lowbit(i)) c[i] += y;
}树状数组的初始化
初始
树状数组的实际应用
1. 树状数组模板
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int a[N], c[N], n, m;
int lowbit(int x) {
return x & -x;
}
void add(int x, int y) {
for (int i = x; i <= n; i += lowbit(i)) c[i] += y;
}
int sum(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
add(i, a[i]);
}
while (m --) {
int op, x, y;
cin >> op >> x >> y;
if (op == 1) {
add(x, y);
} else {
cout << sum(y) - sum(x - 1) << endl;
}
}
return 0;
}2. 求逆序对
问题分析:
- 在序列
的取值范围上建立树状数组,初始全为 。 - 倒序扫描序列
,对于 ,查询树状数组中 的和,即 ,即为 的逆序对个数。 - 更新树状数组,
。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], c[N], n;
int lowbit(int x) {
return x & -x;
}
void add(int x, int y) {
for (int i = x; i <= N; i += lowbit(i)) c[i] += y;
}
int query(int x) {
int res = 0;
for (int i = x; i; i -= lowbit(i)) res += c[i];
return res;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
long long res = 0;
for (int i = n; i > 0; i--) {
res += query(a[i] - 1);
add(a[i], 1);
}
cout << res << endl;
return 0;
}如果序列
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
long long a[N], n, c1[N], c2[N], r1[N], r2[N], l1[N], l2[N];
void add(long long c[], long long x, long long y) {
for (; x <= N; x += x & -x) {
c[x] += y;
}
}
long long query(long long c[], long long x) {
long long ans = 0;
for (; x; x -= x & -x) ans += c[x];
return ans;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = n; i >= 1; i--) {
r1[i] = query(c1, N) - query(c1, a[i]);
add(c1, a[i], 1);
r2[i] = query(c2, a[i] - 1);
add(c2, a[i], 1);
}
memset(c1, 0, sizeof c1);
memset(c2, 0, sizeof c2);
for (int i = 1; i <= n; i++) {
l1[i] = query(c1, N) - query(c1, a[i]);
add(c1, a[i], 1);
l2[i] = query(c2, a[i] - 1);
add(c2, a[i], 1);
}
long long ans1 = 0, ans2 = 0;
for (int i = 1; i <= n; i++) {
ans1 += r1[i] * l1[i];
ans2 += r2[i] * l2[i];
}
cout << ans1 << " " << ans2 << endl;;
return 0;
}树状数组的扩展应用
1. 区间增加,单点查询
问题分析:结合差分的思想,维护指令的影响即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N], c[N];
void add(int x, int y) {
for (; x <= N; x += x & -x) c[x] += y;
}
int query(int x) {
int ans = 0;
for (; x; x -= x & -x) ans += c[x];
return ans;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
while (m--) {
char op[2]; cin >> op;
if (op[0] == 'Q') {
int x; cin >> x;
cout << a[x] + query(x) << endl;
} else {
int l, r, x; cin >> l >> r >> x;
add(l, x);
add(r + 1, -x);
}
}
return 0;
}2. 区间增加,区间查询
在上一题中,我们使用树状数组维护了一个数组 C l r d,我们令
上式可以改写为:
由此,在本题中我们维护两个树状数组
对于 C l r d,我们令
对于 Q l r d,我们令
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
long long a[N], c[2][N], s[N];
int n, m;
int lowbit(int x) {
return x & -x;
}
long long query(int k, int x) {
long long ans = 0;
for (int i = x; i; i -= lowbit(i)) ans += c[k][i];
return ans;
}
void add(int k, int x, int y) {
for (int i = x; i <= n; i += lowbit(i)) c[k][i] += y;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
while (m--) {
char op[2];
int l, r, d;
cin >> op >> l >> r;
if (op[0] == 'Q') {
long long ans = s[r] + (r + 1) * query(0, r) - query(1, r);
ans -= s[l - 1] + l * query(0, l - 1) - query(1, l - 1);
cout << ans << endl;
} else {
cin >> d;
add(0, l, d);
add(0, r + 1, -d);
add(1, l, l * d);
add(1, r + 1, -(r + 1) * d);
}
}
return 0;
}请同学们课后完成:
