树状数组
定义:
相较于线段树大人,小小的树状数组需要的空间不像线段树那样大♂,一般情况下,仅需 \(O(N)\) 的空间复杂度。
下面是结构示意图:

那个...为什么 \(t[6]\) 包含2个元素,而 \(t[8]\) 包含8个元素呢?这...和见过的树不一样...
就是这样!(激动),树状数组最特别的地方便在这。相较于线段树"层层区间有规律的划分",树状数组你第一眼
还真看不出什么,此时,就要介绍区间规划的核心计算函数————\(lowbit\) 了。
lowbit:\(n.\) 低位(垃圾翻译)
从翻译中可以看的出来,这是跟最低位是有关系的,那么是什么最低?是二进制下最低位的"1".
那么怎么求呢?知道和会用可是两回事。
很简单: lowbit(x) = x & -x .
好简便...为什么?
首先得要知道,这里的-x可不是直接反转,那叫反码,这里的应该是补码。
补码在反码的基础上加一,这样使得反码和原码原本可以严丝合缝,1和0互补,最后形成一个全1的数。
但在变成补码后,这么完美的结合被打破了。在反码变成1的位,只能被迫让位反转回去.那么最后会停在哪呢?会停在第一个0的位:因为它踩到缺口了。
由于且运算同舟共济的特性,使得高位的由于没被再次反转所以都是0,还有低位的零在且运算过后还是0,最后只剩下一个位是1,即所求的lowbit.
那么lowbit应用在哪?重新回到结构图,可以发现,每一位所对应的区间长度,恰好对应其lowbit值,
如 \(lowbit(8) = 8\),因为其二进制为 \(1000_2\) ,\(lowbit(6) = 2\),二进制为 \(110_2\) ,由于只取其最低位,所以结果为2.
这就是如此神奇的一个数据结构...不是吗?
实现:
这里以洛谷的P3374为例,主要有几种操作:
-
add(x,k) : 在第 \(x\) 位加上 \(k\) 。
-
sum(x,y) : 区间 \([x,y]\) 内的元素和。
等等,树状数组不是只能存储一段区间的值吗?
你不会去凑吗?
对于操作二,我们采用前缀和的做法,通过凑来凑出对应完整的前缀区间。
为什么不直接挑对应的区间段呢?
第一,这样的操作导致并不能发挥lowbit的特点,无法通过当前节点的lowbit来判断该取哪一段.
第二,如果你真的要这么做,那么我觉得线段树更适合你.
那么,前缀和怎么求?前面提到,每个位置所记录的区间长度为其lowbit的值,那么减去lowbit,不就是还没被计算到的地方了吗?
所以,这么一直减,减到小于1,就代表已经调完了,不用担心重叠,因为每一个位记录的区间长度为其lowbit值。
板子代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,tree[500005],a[500005],j,x,y,z;
int lowbit(int x){
return x & -x;
}
void add(int x,int y){
for(int i = x;i <= n;i += lowbit(i)) tree[i] += y;//加的时候确保每一个记录这个位的都要加
return;
}
int sum(int x){
int ans = 0;
for(int i = x;i;i -= lowbit(i)) ans += tree[i]; //i如果为0,直接退出
return ans;
}
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i++) {
cin >> a[i];
add(i,a[i]);
}
while(m--){
cin >> x >> y >> z;
if(x == 1) add(y,z);
else cout<<sum(z) - sum(y - 1)<<'\n';
}
}

浙公网安备 33010602011771号