1.概述
树状数组(Binary Indexed Trees),其基本用途是维护序列的前缀和。对于给定的序列
a
[
]
a[\ ]
a[ ],我们建立一个数组
c
[
]
c[\ ]
c[ ],其中
c
[
x
]
c[x]
c[x] 保存序列
a
[
]
a[\ ]
a[ ] 的区间
[
x
−
l
o
w
b
i
t
(
x
)
+
1
,
x
]
[x - lowbit(x) + 1, x]
[x−lowbit(x)+1,x] 中所有数的和。
事实上,数组
c
[
]
c[\ ]
c[ ] 可以看作一个如下图所示的树形结构,图中最下边一行是
N
N
N 个叶节点
(
N
=
16
)
(N= 16)
(N=16),代表数值
a
[
1
∼
N
]
a[1 \sim N]
a[1∼N]。该结构满足以下性质:
-
每个内部节点
c
[
x
]
c[x]
c[x] 保存以它为根的子树中所有叶节点的和。
-
每个内部节点
c
[
x
]
c[x]
c[x] 的子节点个数等于
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x) 的位数。
-
除树根外,每个内部节点
c
[
x
]
c[x]
c[x] 的父节点是
c
[
x
+
l
o
w
b
i
t
(
x
)
]
c[x + lowbit(x)]
c[x+lowbit(x)]。
-
树的深度为
O
(
log
N
)
O(\log N)
O(logN)。
-
如果
N
N
N 不是
2
2
2 的整次幂,那么树状数组就是一个具有同样性质的森林结构。
2.原理
(1) 对于一个普通的二叉树,叶子结点代表数组
a
[
]
a[\ ]
a[ ] 的
a
[
1
]
∼
a
[
8
]
a[1] \sim a[8]
a[1]∼a[8]。
(2) 将其进行简单的变形。
(3) 然后定义每一列的顶端结点数组
c
[
]
c[\ ]
c[ ],令
c
[
i
]
c[i]
c[i] 代表子树的叶结点的权值之和。
-
c
[
1
]
=
a
[
1
]
c[1] = a[1]
c[1]=a[1]
-
c
[
2
]
=
a
[
1
]
+
a
[
2
]
c[2] = a[1] + a[2]
c[2]=a[1]+a[2]
-
c
[
3
]
=
a
[
3
]
c[3] = a[3]
c[3]=a[3]
-
c
[
4
]
=
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
c[4] = a[1] + a[2] + a[3] + a[4]
c[4]=a[1]+a[2]+a[3]+a[4]
-
c
[
5
]
=
a
[
5
]
c[5] = a[5]
c[5]=a[5]
-
c
[
6
]
=
a
[
5
]
+
a
[
6
]
c[6] = a[5] + a[6]
c[6]=a[5]+a[6]
-
c
[
7
]
=
a
[
7
]
c[7] = a[7]
c[7]=a[7]
-
c
[
8
]
=
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
+
a
[
5
]
+
a
[
6
]
+
a
[
7
]
+
a
[
8
]
c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8]
c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]
(4) 将数组
c
[
]
c[\ ]
c[ ] 的结点序号转化为二进制。
-
1
=
(
001
)
,
c
[
1
]
=
a
[
1
]
1 = (001), c[1] = a[1]
1=(001),c[1]=a[1]
-
2
=
(
010
)
,
c
[
2
]
=
a
[
1
]
+
a
[
2
]
2 = (010), c[2] = a[1] + a[2]
2=(010),c[2]=a[1]+a[2]
-
3
=
(
011
)
,
c
[
3
]
=
a
[
3
]
3 = (011), c[3] = a[3]
3=(011),c[3]=a[3]
-
4
=
(
100
)
,
c
[
4
]
=
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
4 = (100), c[4] = a[1] + a[2] + a[3] + a[4]
4=(100),c[4]=a[1]+a[2]+a[3]+a[4]
-
5
=
(
101
)
,
c
[
5
]
=
a
[
5
]
5 = (101), c[5] = a[5]
5=(101),c[5]=a[5]
-
6
=
(
110
)
,
c
[
6
]
=
a
[
5
]
+
a
[
6
]
6 = (110), c[6] = a[5] + a[6]
6=(110),c[6]=a[5]+a[6]
-
7
=
(
111
)
,
c
[
7
]
=
a
[
7
]
7 = (111), c[7] = a[7]
7=(111),c[7]=a[7]
-
8
=
(
1000
)
,
c
[
8
]
=
a
[
1
]
+
a
[
2
]
+
a
[
3
]
+
a
[
4
]
+
a
[
5
]
+
a
[
6
]
+
a
[
7
]
+
a
[
8
]
8 = (1000), c[8] = a[1] + a[2] + a[3] + a[4] + a[5] + a[6] + a[7] + a[8]
8=(1000),c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8]
可以发现
c
[
i
]
=
a
[
i
−
2
k
+
1
]
+
a
[
i
−
2
k
+
2
]
+
.
.
.
.
.
.
+
a
[
i
]
c[i]=a[i-2^k+1]+a[i-2^k+2]+......+a[i]
c[i]=a[i−2k+1]+a[i−2k+2]+......+a[i],其中,
k
k
k 为
i
i
i 的二进制中从最低位到高位连续零的长度。
可见,树状数组根本上来说就是二进制的应用。
要注意的是,树状数组只能计算
a
[
1
]
a[1]
a[1] 开始的和,
a
[
0
]
a[0]
a[0] 这个元素是不能用的。
3.实现
3.1 lowbit(x)
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x) 是
x
x
x 的二进制表达式中最低位的
1
1
1 所对应的值,换言之,
l
o
w
b
i
t
(
x
)
=
2
k
lowbit(x)=2^k
lowbit(x)=2k,
k
k
k 为
i
i
i 的二进制中从最低位到高位连续零的长度。
对于一个数
x
x
x,想求
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x),可借助
x
x
x 的负数
−
x
-x
−x,进行按位与运算。
-
x
=
6
=
(
0110
)
x = 6 = (0110)
x=6=(0110),此时
k
=
1
k=1
k=1
-
−
x
=
−
6
=
(
1010
)
-x = -6 = (1010)
−x=−6=(1010)
-
x
x
x &
(
−
x
)
=
(
0010
)
=
2
1
(-x) = (0010) = 2^1
(−x)=(0010)=21
// 返回x的二进制最右边1所对应的值
int lowbit(int x)
{
return x & -x;
}
l
o
w
b
i
t
(
x
)
lowbit(x)
lowbit(x) 在树状数组中的应用:
c
[
i
]
=
a
[
i
−
2
k
+
1
]
+
a
[
i
−
2
k
+
2
]
+
.
.
.
.
.
.
+
a
[
i
]
=
a
[
i
−
l
o
w
b
i
t
(
i
)
+
1
]
+
a
[
i
−
l
o
w
b
i
t
(
i
)
+
2
]
+
.
.
.
.
.
.
+
a
[
i
]
\begin{aligned} c[i] & = a[i-2^k+1]+a[i-2^k+2]+......+a[i] \\ & = a[i-lowbit(i)+1]+a[i-lowbit(i)+2]+......+a[i] \end{aligned}
c[i]=a[i−2k+1]+a[i−2k+2]+......+a[i]=a[i−lowbit(i)+1]+a[i−lowbit(i)+2]+......+a[i]
3.2 查询前缀和
树状数组支持的基本操作有两个,第一个操作是查询前缀和,即序列
a
[
]
a[\ ]
a[ ] 中第
1
∼
x
1 \sim x
1∼x 个数的和。
首先求出
x
x
x 的二进制表示中每个等于
1
1
1 的位,然后把
[
1
,
x
]
[1, x]
[1,x] 分成
O
(
log
N
)
O(\log N)
O(logN) 个小区间,而每个小区间的区间和都已经保存在数组
c
[
]
c[\ ]
c[ ] 中了。
下面代码在
O
(
log
N
)
O(\log N)
O(logN) 的时间内查询前缀和:
// 返回a[1]+...+a[x]的和
int ask(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += c[i];
}
return res;
}
举例:求
a
[
1
]
+
a
[
2
]
+
.
.
.
+
a
[
7
]
a[1] + a[2] + ... + a[7]
a[1]+a[2]+...+a[7] 的和
-
7
(
111
)
7(111)
7(111),res += c[7]
-
7
−
l
o
w
b
i
t
(
7
)
=
7
−
l
o
w
b
i
t
(
111
)
=
7
−
1
=
6
(
110
)
7-lowbit(7) = 7-lowbit(111) = 7-1 = 6(110)
7−lowbit(7)=7−lowbit(111)=7−1=6(110),res += c[6]
-
6
−
l
o
w
b
i
t
(
6
)
=
6
−
l
o
w
b
i
t
(
110
)
=
6
−
2
=
4
(
100
)
6-lowbit(6) = 6-lowbit(110) = 6-2 = 4(100)
6−lowbit(6)=6−lowbit(110)=6−2=4(100),res += c[4]
-
4
−
l
o
w
b
i
t
(
4
)
=
4
−
l
o
w
b
i
t
(
100
)
=
4
−
4
=
0
(
000
)
4-lowbit(4) = 4-lowbit(100) = 4-4 = 0(000)
4−lowbit(4)=4−lowbit(100)=4−4=0(000)
举例:求
a
[
1
]
+
a
[
2
]
+
.
.
.
+
a
[
5
]
a[1] + a[2] + ... + a[5]
a[1]+a[2]+...+a[5] 的和
-
5
(
101
)
5(101)
5(101),res += c[5]
-
5
−
l
o
w
b
i
t
(
5
)
=
5
−
l
o
w
b
i
t
(
101
)
=
5
−
1
=
4
(
100
)
5-lowbit(5) = 5-lowbit(101) = 5-1 = 4(100)
5−lowbit(5)=5−lowbit(101)=5−1=4(100),res += c[4]
-
4
−
l
o
w
b
i
t
(
4
)
=
4
−
l
o
w
b
i
t
(
100
)
=
4
−
4
=
0
(
000
)
4-lowbit(4) = 4-lowbit(100) = 4-4 = 0(000)
4−lowbit(4)=4−lowbit(100)=4−4=0(000)
当然,若要查询序列
a
[
]
a[\ ]
a[ ] 的区间
[
l
,
r
]
[l,r]
[l,r] 中所有数的和,只需计算 ask(r) - ask(l - 1)
。
3.3 单点增加
树状数组支持的第二个基本操作是单点增加,意思是给序列中的一个数
a
[
x
]
a[x]
a[x] 加上
y
y
y,同时正确维护序列的前缀和。
根据上面给出的树形结构和它的性质,只有节点
c
[
x
]
c[x]
c[x] 及其所有祖先节点保存的“区间和”包含
a
[
x
]
a[x]
a[x],而任意一个节点的祖先至多只有
log
N
\log N
logN 个,我们逐一对它们的
c
c
c 值进行更新即可。
下面代码在
O
(
log
N
)
O(\log N)
O(logN) 的时间内执行单点增加操作:
// 令a[x] += y
void add(int x, int y)
{
for (int i = x; i <= n; i += lowbit(i))
{
c[i] += y;
}
}
如图所示,当更新
a
[
1
]
a[1]
a[1] 时,需要向上更新
c
[
1
]
c[1]
c[1]、
c
[
2
]
c[2]
c[2]、
c
[
4
]
c[4]
c[4]、
c
[
8
]
c[8]
c[8],则有:
-
1
(
001
)
1(001)
1(001),c[1] += a[1]
-
1
+
l
o
w
b
i
t
(
1
)
=
1
+
l
o
w
b
i
t
(
001
)
=
1
+
1
=
2
(
010
)
1+lowbit(1) = 1+lowbit(001) = 1+1 = 2(010)
1+lowbit(1)=1+lowbit(001)=1+1=2(010),c[2] += a[1]
-
2
+
l
o
w
b
i
t
(
2
)
=
2
+
l
o
w
b
i
t
(
010
)
=
2
+
2
=
4
(
100
)
2+lowbit(2) = 2+lowbit(010) = 2+2 = 4(100)
2+lowbit(2)=2+lowbit(010)=2+2=4(100),c[4] += a[1]
-
4
+
l
o
w
b
i
t
(
4
)
=
4
+
l
o
w
b
i
t
(
100
)
=
4
+
4
=
8
(
1000
)
4+lowbit(4) = 4+lowbit(100) = 4+4 = 8(1000)
4+lowbit(4)=4+lowbit(100)=4+4=8(1000),c[8] += a[1]
可以发现:更新过程是查询过程的逆过程。
4.初始化
在执行所有操作之前,我们需要对树状数组进行初始化,即针对原始序列
a
[
]
a[\ ]
a[ ] 构造一个树状数组
c
[
]
c[\ ]
c[ ]。
为了简便,比较一般的初始化方法是:直接建立一个全为
0
0
0 的数组
c
[
]
c[\ ]
c[ ],然后对每个位置
x
x
x 执行 add(x, a[x])
,就完成了对原始序列
a
[
]
a[\ ]
a[ ] 构造树状数组的过程,时间复杂度为
O
(
N
log
N
)
O(N \log N)
O(NlogN)。通常采用这种初始化方法已经足够。
更高效的初始化方法是:从小到大依次考虑每个节点
x
x
x,借助
l
o
w
b
i
t
lowbit
lowbit 运算扫描它的子节点并求和。若采用这种方法,上面树形结构中的每条边只会被遍历一次,时间复杂度为
O
(
N
)
O(N)
O(N)。