树状数组
引入
如果,让你求一段数的和,你会怎么求?
你当然可以用前缀和,在 \({O(n)}\) 的预处理后查询只需要 \({O(1)}\) 的复杂度。
但是,如果我还要修改呢?
比如说最不理想的情况下,我每次都修改第一个数,前缀和数组每次就要花 \({O(n)}\) 的时间复杂度来维护。
我如果修改m次,你就要花 \({O(nm)}\) 复杂度,太大了。
那么,有个东西叫做树状数组,它使用内存相当于线性数组,同时又能建立一个类似树的结构,在求前缀和时的复杂度和修改的复杂度都是 \({log\ n}\) 级别的,复杂度好多了。
结构
定义一个线性数组,
每一位数组管辖着从自身开始往前的某一段范围,有些类似于线段树。
但怎么规定好这个范围?这就涉及树状数组的核心操作——\({lowbit}\).
lowbit
为该数二进制下最后一个1和其后的0所表示的十进制数。
即 \({x \& (-x) }\)
你要找前驱时就是 \({i-lowbit(i)}\)
父节点是 \({i+lowbit(i)}\)
这利用了二进制的特性,在此不作过多解释。
因此,修改时一般是从下往上,而访问时则相反,是从上往下。
附:
修改code:
void update(int pos,int k) {
for(int i=pos;i<=n;i+=lowbit(pos))
b[pos]+=k;
return ;
}
查询code:
void query(int pos) {
for(int i=pos;i>=1;i-=lowbit(pos))
sum+=b[pos];
return ;
}