一、前置知识
-
在树状数组中会用到一种位运算 : l o w b i t ( x ) = x & ( − x ) lowbit(x) = x \& (-x) lowbit(x)=x&(−x)。下面来简单说明一下这种位运算的作用。
-
我们知道在计算机中,数值都是以补码形式存储的。所以对于一个正整数x来说,在计算机中 : -x = ~x + 1。
我们假设x的二进制表示末位有k个0,此时x和-x可以看作:
( x ) D = ( x x x 1 00...0 ⏞ k个0 ) B (x)_D=(xxx1\overbrace{00...0}^\text{k个0})_B (x)D=(xxx100...0 k个0)B
( − x ) D = ( y y y 1 00...0 ⏞ k个0 ) B (-x)_D=(yyy1\overbrace{00...0}^\text{k个0})_B (−x)D=(yyy100...0 k个0)B
其中 x x x表示未知数, y y y表示 x x x取反之后的数。
此时我们可以看出
x & ( − x ) = 1 00...0 ⏞ k个0 x \& (-x)=1\overbrace{00...0}^\text{k个0} x&(−x)=100...0 k个0 -
从以上分析可以看出:
l o w b i t ( x ) = x & ( − x ) = 2 k lowbit(x) = x\&(-x)=2^{k} lowbit(x)=x&(−x)=2k
二、树状数组简介
1. 作用
树状数组主要用来解决前缀和的问题。
2. 操作
树状数组有两个核心操作:
-
单点修改
-
区间查询
3. 存储方式
顾名思义,用数组去存树状数组。如果用a[ ]表示原数组,c[ ]表示树状数组,则c[i]表示的是这样一段和:
c
[
i
]
=
a
[
i
−
l
o
w
b
i
t
(
i
)
+
1
]
+
a
[
i
−
l
o
w
b
i
t
(
i
)
+
2
]
+
…
+
a
[
i
]
=
∑
j
=
i
−
l
o
w
b
i
t
(
i
)
+
1
i
a
[
j
]
c[i]=a[i-lowbit(i) + 1]+a[i-lowbit(i)+2]+…+a[i]=\sum_{j = i - lowbit(i) + 1}^ia[j]
c[i]=a[i−lowbit(i)+1]+a[i−lowbit(i)+2]+…+a[i]=j=i−lowbit(i)+1∑ia[j]
ps : 数组下标要从1开始(在代码部分会有说明)
4. 树状数组图示
4.优点
- 代码简单,容易调试
- 修改操作和查询操作的时间复杂度都是O(logn)
三、代码部分
- 单点修改
/* 在x处加上v */
void add(int x, int v)
for(int i = x; i <= n; i += lowbit(i)) // x改变,所有包含x的c[]全部都要加上v
c[i] += v;
}
- 区间查询
/* 查询[1, x]的前缀和 */
int query(int x) {
int res = 0;
for(int i = x; i; i -= lowbit(i))
res += c[i];
return res;
}
// 这里说明一下为什么树状数组下标从1开始:
// 假如从0开始, 当i = 0的时候, lowbit(0) = 0, 会陷入死循环
四、应用及题目
- 树状数组模板题:AcWing 1264 : 动态求连续区间和
- 动态查询乱序数组中的中间值:PAT 1057 : Stack
- (这个不知道叫什么应用, 直接贴链接^ ^):AcWing 1215 : 小朋友排队
- …