一、简介
树状数组用于两种操作:
- 快速求前缀和 O ( l o g n ) O(logn) O(logn)
- 修改某一个数 O ( l o g n ) O(logn) O(logn)
这两个操作也可以用其他方法结构完成:
用一个数组存每个数:操作1.
O
(
n
)
O(n)
O(n),遍历前n个数求和;操作2.
O
(
1
)
O(1)
O(1),直接修改即可
维护前缀和一个数组:操作1.
O
(
1
)
O(1)
O(1);操作2.
O
(
n
)
O(n)
O(n)
可见其他结构最坏情况都是 O ( n ) O(n) O(n)
二、算法
已知一个数可以拆成若干个2的幂次之和
x
=
2
i
1
+
2
i
2
+
2
i
3
+
.
.
.
+
2
i
k
x=2^{i_1}+2^{i_2}+2^{i_3}+...+2^{i_k}
x=2i1+2i2+2i3+...+2ik
其中
i
1
≤
i
2
≤
.
.
.
≤
i
k
i_1 \leq i_2 \leq ... \leq i_k
i1≤i2≤...≤ik
所以区间
(
0
,
x
]
(0, x]
(0,x]可以拆成
(
x
−
2
i
1
,
x
]
(x-2^{i_1},x]
(x−2i1,x]
(
x
−
2
i
1
−
2
i
2
,
x
−
2
i
1
]
(x-2^{i_1}-2^{i_2},x-2^{i_1}]
(x−2i1−2i2,x−2i1]
.
.
.
(
x
−
2
i
1
−
2
i
2
−
.
.
.
−
2
i
k
,
x
−
2
i
1
−
2
i
2
−
.
.
.
−
2
i
k
−
1
]
(x-2^{i_1}-2^{i_2}-...-2^{i_k},x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}]
(x−2i1−2i2−...−2ik,x−2i1−2i2−...−2ik−1]
也就是
(
0
,
x
−
2
i
1
−
2
i
2
−
.
.
.
−
2
i
k
−
1
]
(0,x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}]
(0,x−2i1−2i2−...−2ik−1]
再来看这些区间包含多少个数
(
x
−
2
i
1
,
x
]
(x-2^{i_1},x]
(x−2i1,x]包含
2
i
1
2^{i_1}
2i1个数,其中
i
1
i_1
i1是
x
x
x二进制表示的最后一位1的位置
(
x
−
2
i
1
−
2
i
2
,
x
−
2
i
1
]
(x-2^{i_1}-2^{i_2},x-2^{i_1}]
(x−2i1−2i2,x−2i1]包含
2
i
2
2^{i_2}
2i2个数,其中
i
2
i_2
i2是
x
−
2
i
1
x-2^{i_1}
x−2i1二进制表示的最后一位1的位置
(
0
,
x
−
2
i
1
−
2
i
2
−
.
.
.
−
2
i
k
−
1
]
(0,x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}]
(0,x−2i1−2i2−...−2ik−1]包含
2
i
k
2^{i_k}
2ik个数,其中
i
k
i_k
ik是
x
−
2
i
1
−
2
i
2
−
.
.
.
−
2
i
k
−
1
x-2^{i_1}-2^{i_2}-...-2^{i_{k-1}}
x−2i1−2i2−...−2ik−1二进制表示的最后一位1的位置。
性质1:对于区间 ( L , R ] (L,R] (L,R],区间包含的数的个数是R的二进制表示的最后一位1的位置对应次幂。
这个最后一位1的位置对应次幂可以用lowbit(x) = x & (-x)
计算求得,因为计算机里的负数就是对原数的二进制取反+1
例如:
6
=
11
0
2
6=110_2
6=1102,
−
6
=
01
0
2
-6=010_2
−6=0102,lowbit(6) = 110 & 010 = 10
,即6最后一位1对应次幂是
1
0
2
=
2
1
10_2=2^1
102=21
再来看如何拆分区间:
x
=
6
=
2
2
+
2
1
x=6=2^2+2^1
x=6=22+21,二进制是
11
0
2
110_2
1102,因此
i
1
=
1
i_1=1
i1=1,
i
2
=
2
i_2=2
i2=2
所以区间
(
0
,
6
]
(0, 6]
(0,6]可以拆成:
(
6
−
2
1
,
6
]
=
(
4
,
6
]
(6-2^1,6]=(4,6]
(6−21,6]=(4,6],其中
i
1
=
1
i_1=1
i1=1是
6
6
6二进制表示
110
110
110最后一位1的位置
(
6
−
2
1
−
2
2
,
x
−
2
2
]
=
(
0
,
4
]
(6-2^1-2^2,x-2^2]=(0,4]
(6−21−22,x−22]=(0,4],其中
i
2
=
2
i_2=2
i2=2是
4
4
4二进制表示
100
100
100最后一位1的位置
有了性质1,可以得到 L = R − l o b i t ( R ) L=R-lobit(R) L=R−lobit(R),故区间为 ( R − l o b i t ( R ) , R ] (R-lobit(R),R] (R−lobit(R),R]
用数组
t
[
x
]
t[x]
t[x]表示区间
[
x
−
l
o
b
i
t
(
x
)
+
1
,
x
]
[x-lobit(x)+1, x]
[x−lobit(x)+1,x]的和,则
t
[
0
]
t[0]
t[0]~
t
[
8
]
t[8]
t[8]如下所示
可以看到形成一个类似树的结构,例如t[8]的孩子就有
t
[
4
]
,
t
[
6
]
,
t
[
7
]
t[4], t[6], t[7]
t[4],t[6],t[7]。
这个树有如下性质:
性质2:
通过父节点
t
[
x
]
t[x]
t[x]找子节点:令
x
′
=
x
−
1
x'=x-1
x′=x−1,之后不断去掉
x
′
x'
x′的最后一位1即可。
即对于
x
=
.
.
.
10...00
0
2
x=...10...000_2
x=...10...0002,得到
x
′
=
x
−
1
=
.
.
.
01...11
1
2
x'=x-1=...01...111_2
x′=x−1=...01...1112,所以有孩子
.
.
.
01...11
1
2
...01...111_2
...01...1112、
.
.
.
01...11
0
2
...01...110_2
...01...1102、
.
.
.
01...10
0
2
...01...100_2
...01...1002、
.
.
.
01...00
0
2
...01...000_2
...01...0002、…、
.
.
.
00...00
0
2
...00...000_2
...00...0002
例如
t
[
8
]
t[8]
t[8],得到
x
′
=
100
0
2
−
1
2
=
11
1
2
x'=1000_2-1_2=111_2
x′=10002−12=1112,则孩子有
t
[
11
1
2
]
,
t
[
11
0
2
]
,
t
[
10
0
2
]
t[111_2],t[110_2],t[100_2]
t[1112],t[1102],t[1002]也就是
t
[
7
]
,
t
[
6
]
,
t
[
4
]
t[7],t[6],t[4]
t[7],t[6],t[4]
通过性质2,可以由孩子求出父亲,例如
t
[
8
]
=
a
[
8
]
+
t
[
4
]
+
t
[
6
]
+
t
[
7
]
t[8]=a[8]+t[4]+t[6]+t[7]
t[8]=a[8]+t[4]+t[6]+t[7]
查询a[1] + ... + a[x]
-> for(int i = x; i > 0; i -= lobit(i)) sum += t[i]
性质3:
通过子节点找父节点:将父节点找子节点过程逆过来即可
对于孩子
x
=
.
.
.
01...10
0
2
x=...01...100_2
x=...01...1002,父亲一定是
.
.
.
10...00
0
2
...10...000_2
...10...0002,可以看到孩子只有唯一一个父亲
所以只需
.
.
.
01...10
0
2
+
.
.
.
00...10
0
2
...01...100_2+...00...100_2
...01...1002+...00...1002即可得到父亲
.
.
.
10...000
...10...000
...10...000
也就是x + lobit(x)
通过性质3,可以找到更新原数组时所需要更新的对应的树状数组
原数组修改a[x] += c
-> 树状数组修改for(int i = x; i <= n; i += lobit(i)) t[i] += c
注意:树状数组维护的位置下表只可以从1开始,如果为0则的lowbit也是0,会死循
三、例题
注意
#include <iostream>
using namespace std;
const int N = 500005;
int n, m;
int t[N], a[N];
int lowbit(int x) {
return x & (-x);
}
int sum(int x) {
int s = 0;
for (int i = x; i > 0; i -= lowbit(i)) s += t[i];
return s;
}
void add(int x, int c) {
for (int i = x; i <= n; i += lowbit(i)) {
t[i] += c;
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) {
scanf("%d", &a[i]);
add(i, a[i]);
}
int op, x, y;
for (int i = 1; i <= m; i ++ ) {
scanf("%d%d%d", &op, &x, &y);
if (op == 1) {
add(x, y);
}
else {
printf("%d\n", sum(y) - sum(x - 1));
}
}
return 0;
}