定义
树状数组用于解决区间上的更新、求和、求最值问题,更新和查询的时间复杂度均为O(logN)。
可以看出:
- tree[1] = arr[1]
- tree[2] = arr[1] + arr[2]
- tree[3] = arr[3]
- tree[4] = arr[1] + arr[2] + arr[3] + arr[4]
- …
将每个值写成二进制形式:
可以看出:
- 如果要求和arr[1~n],实际上就是每次将n的二进制形式最后一个1去掉,将相应的tree结点相加(如arr[0111] = tree[0111] + tree[0110] + tree[0100])。
- 如果要修改arr[n],实际上就是每次加上n的二进制形式最后一个1,将对应的tree结点相加(如arr[1001]蕴含在tree[1001]、tree[1010]、tree[1100]中)。
n的二进制形式的最后一个1,可以用以下方法得到:n & -n
例题一:单点修改、区间求和
洛谷P3374
已知一个数组,不断进行下面两种操作:
- 将某个数加上k
- 求出某个区间的和
#include <iostream>
#include <vector>
#include <cstdio>
#include <string>
#include <map>
#include <set>
#include <cstring>
#include <climits>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <cassert>
using namespace std;
#define LOWBIT(x) ((x) & -(x))
const int MAXN = 500005;
typedef long long ll;
int n, m;
ll tree[MAXN];
void update(int idx, ll val) {
while (idx <= n) {
tree[idx] += val;
idx += LOWBIT(idx);
}
}
ll query(int idx) {
ll sum = 0;
while (idx > 0) {
sum += tree[idx];
idx ^= LOWBIT(idx);
}
return sum;
}
int main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
#endif
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
ll val;
scanf("%lld", &val);
update(i, val);
}
while (m--) {
int op;
scanf("%d", &op);
if (op == 1) {
int idx;
ll val;
scanf("%d%lld", &idx, &val);
update(idx, val);
} else if (op == 2) {
int begin, end;
scanf("%d%d", &begin, &end);
printf("%lld\n", query(end) - query(begin - 1));
}
}
return 0;
}
例题二:区间修改、单点查询
洛谷P3368
已知一个数组,不断进行下面两种操作:
- 将某个区间每个数都加上k
- 查询某个数的值
思路:
设 arr 为原数组,差分数组 diff 定义为:diff[i] = arr[i] - arr[i-1]
使用树状数组维护diff数组,那么:
- 区间修改:修改 arr[a~b] 等效于修改 diff[a] 和 diff[b+1]。
- 单点查询:查询 arr[n] 等效于求和 diff[1~n]。
#include <iostream>
#include <vector>
#include <cstdio>
#include <string>
#include <map>
#include <set>
#include <cstring>
#include <climits>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <cassert>
using namespace std;
#define LOWBIT(x) ((x) & -(x))
const int MAXN = 500005;
typedef long long ll;
int n, m;
ll tree[MAXN];
void update(int idx, ll val) {
while (idx <= n) {
tree[idx] += val;
idx += LOWBIT(idx);
}
}
ll query(int idx) {
ll sum = 0;
while (idx > 0) {
sum += tree[idx];
idx ^= LOWBIT(idx);
}
return sum;
}
int main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
#endif
scanf("%d%d", &n, &m);
ll last = 0;
for (int i = 1; i <= n; ++i) {
ll val;
scanf("%lld", &val);
update(i, val - last);
last = val;
}
while (m--) {
int op;
scanf("%d", &op);
if (op == 1) {
int begin, end;
ll val;
scanf("%d%d%lld", &begin, &end, &val);
update(begin, val);
update(end + 1, -val);
} else if (op == 2) {
int idx;
scanf("%d", &idx);
printf("%lld\n", query(idx));
}
}
return 0;
}
例题三:区间修改、区间求和
POJ 3468
已知一个数组,不断进行下面两种操作:
- 将某个区间每个数都加上k
- 求出某个区间的和
思路:
利用之前的差分数组,如果要求和arr[1~n],
a
r
r
[
1
,
n
]
=
d
i
f
f
[
1
]
+
(
d
i
f
f
[
1
]
+
d
i
f
f
[
2
]
)
+
(
d
i
f
f
[
1
]
+
d
i
f
f
[
2
]
+
d
i
f
f
[
3
]
)
+
.
.
.
+
(
d
i
f
f
[
1
]
+
d
i
f
f
[
2
]
+
.
.
.
+
d
i
f
f
[
n
]
)
arr[1,n] = diff[1] + (diff[1] + diff[2]) + (diff[1] + diff[2] + diff[3]) + ... + (diff[1] + diff[2] + ... + diff[n])
arr[1,n]=diff[1]+(diff[1]+diff[2])+(diff[1]+diff[2]+diff[3])+...+(diff[1]+diff[2]+...+diff[n])
=
n
⋅
d
i
f
f
[
1
]
+
(
n
−
1
)
d
i
f
f
[
2
]
+
(
n
−
2
)
d
i
f
f
[
3
]
+
.
.
.
+
d
i
f
f
[
n
]
=n\cdot diff[1] + (n-1)diff[2] + (n-2)diff[3] + ... + diff[n]
=n⋅diff[1]+(n−1)diff[2]+(n−2)diff[3]+...+diff[n]
=
n
(
d
i
f
f
[
1
]
+
d
i
f
f
[
2
]
+
.
.
.
+
d
i
f
f
[
n
]
)
−
(
0
⋅
d
i
f
f
[
1
]
+
1
⋅
d
i
f
f
[
2
]
+
2
⋅
d
i
f
f
[
3
]
+
.
.
.
+
(
n
−
1
)
d
i
f
f
[
n
]
)
=n(diff[1] + diff[2] + ... + diff[n]) - (0\cdot diff[1] + 1\cdot diff[2] + 2\cdot diff[3] + ... + (n-1)diff[n])
=n(diff[1]+diff[2]+...+diff[n])−(0⋅diff[1]+1⋅diff[2]+2⋅diff[3]+...+(n−1)diff[n])
因此可以维护两个树状数组,diff[i] 和 (i-1)diff[i]。
#include <iostream>
#include <vector>
#include <cstdio>
#include <string>
#include <map>
#include <set>
#include <cstring>
#include <climits>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <cassert>
using namespace std;
#define LOWBIT(x) ((x) & -(x))
const int MAXN = 100005;
typedef long long ll;
int n, m;
ll tree[MAXN];
ll tree2[MAXN];
void update(ll _tree[], int idx, ll val) {
while (idx <= n) {
_tree[idx] += val;
idx += LOWBIT(idx);
}
}
ll query(const ll _tree[], int idx) {
ll sum = 0;
while (idx > 0) {
sum += _tree[idx];
idx ^= LOWBIT(idx);
}
return sum;
}
int main() {
#ifdef DEBUG
freopen("in.txt", "r", stdin);
#endif
scanf("%d%d", &n, &m);
ll last = 0;
for (int i = 1; i <= n; ++i) {
ll val;
scanf("%lld", &val);
update(tree, i, val - last);
update(tree2, i, (i - 1) * (val - last));
last = val;
}
char op[5];
while (m--) {
scanf("%s", op);
if (op[0] == 'C') {
int begin, end;
ll val;
scanf("%d%d%lld", &begin, &end, &val);
update(tree, begin, val);
update(tree, end + 1, -val);
update(tree2, begin, (begin - 1) * val);
update(tree2, end + 1, end * -val);
} else if (op[0] == 'Q') {
int begin, end;
scanf("%d%d", &begin, &end);
ll query_begin = (begin - 1) * query(tree, begin - 1) - query(tree2, begin - 1);
ll query_end = end * query(tree, end) - query(tree2, end);
printf("%lld\n", query_end - query_begin);
}
}
return 0;
}