本文是高级数据结构系列第3篇。
从这一篇开始,是一些区间数据结构。
引入
维护一个数列
a
,开始时数列中每一个数都是
- A x y:表示把 ax 加上 y 。
- Q l r:表示询问
∑ri=lai 的值。
数列长度为
n
,操作总个数为
使用各种数据结构的时间复杂度如下表:
原数组 | 前缀和 | 树状数组 | |
---|---|---|---|
A操作 | O(1) | O(n) | O(log(n)) |
Q操作 | O(n) | O(1) | O(log(n)) |
从上表看出,无论是对于哪种操作,树状数组都有不俗的时间复杂度表现,因此,树状数组擅长解决这个问题。
介绍
来观察一下这个图。
我们假设原数组是 {a} ,而树状数组是 {c} 。则 c1=a1 , c2=a1+a2 , c3=a3 , c4=a1+a2+a3+a4 ,…在这个图中, c12 是 a9 到 a12 的和, c8 是 ∑8i=1ai ,其它以此类推。
如果我们把
c
数组中的下标用二进制表示出来的话,可以发现,
以上内容部分参考百度百科
然后问题就是,怎么知道这个实际数值呢?
根据二进制补码的原理,我们知道
12
&
(−12)=4
,这里的&是位与符号。方便起见,我们把
i
&
修改与查询
修改
讲了这么多,终于进入正题了。再看一眼上面那个图:
如果我们要把 a7 加1,那么c中的那些数也要加1呢?
答案是, c7 , c8 , c16 。因为这些都包含了 a7 。
根据观察,发现下标每次增加的量也是 lowbit(i) 。容易写出以下代码:
inline int lowbit(int x){
return x&-x;
}
inline void add(int x,int y){
//向a[x]加y。
while (x<=n){
c[x]+=y;
x+=lowbit(x);
}
}
查询
和修改十分类似。自己观察上图。
inline int ask(int x){
int ans=0;
while (x){
ans+=c[x];
x-=lowbit(x)
}
}
应用
- 求逆序对
- 等等