树状数组(Fenwick Tree)
以其发明者命名
参考动画: 【五分钟丝滑动画讲解 | 树状数组】
由线段树演变过来,看完动画理解更透彻
树状数组的两个基本功能:查询前缀和,以及单点修改
注:a是原数组,b是树状数组
你会发现,如果只要求前缀和,那其实线段树的很多空间是不必要的,如果要求前三个数的前缀和b[3]
,那只要b[2]+a[3]
即可,并不需要储存a[2]
,
同理,整个线段树的偶数部分都可以删掉。
lowbit函数:作用是快速获取一个整数在二进制下表示最低位的1所表示的值;
int lowbit(int x){
return x&(-x);
}
&是按位与操作,’-x‘是’x‘的二进制补码
正数的补码是其本身,负数的补码是其绝对值的二进制按位取反后加1。
结论1、序号为i的序列正好就是长度为lowBit(i)且以i结尾的序列
这句话描述了树状数组的一个关键特性:每个位置i
的元素负责存储一个特定长度(由lowbit(i)
确定)的区间和,这个区间以i
为结尾。
这句话实质上描述了树状数组存储的信息结构:每个索引i
不直接存储单个元素的值,而是存储从i
向前数lowBit(i)
个元素的累加和。这意味着,树状数组的每个节点负责一个区间的和,而这个区间的长度和起点取决于索引i
的二进制表示。
如何通过树状数组求解前缀和:
计算前14个元素的和pre[14]
:(b是树状数组)
lowbit(14)=2;
14-lowbit(14)=12;
可以得出:pre[14]=pre[12]+b[14]
;
同理,pre[12]
也可以递归得出
int query(int p){
int result=0;
while(p!=0){
result+=b[p];
p-=lowbit(p);
}
return result;
}
结论2、序列b[i]
的上方,正好就是b[i+lowBit(i)]
根据结论可推,如果要修改一个单点元素,只要不断加上lowBit(i)就可以找到上方的所有序列,进行修改
代码如下:
void add(int p,int x){
while(p<N){//p是要更新元素的索引,x是要增加的值
b[p]+=x;
p+=lowbit(p);
}
}
总结
树状数组代码如下:
vector<int> b(N,0);
int lowbit(int x){//获取元素二进制最低位
return x&(-x);
}
//结论二,更新/增加值
void add(int p,int x){![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/03da1813645a4188a28a48a8825778e3.png#pic_center)
while(p<N){//N为原数组的总个数,最大索引值
b[p]+=x;
p+=lowbit[p];
}
}
//结论一,查询
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=b[i];
return res;
}
应用:树状数组计算逆序对
[[2024-03-26(逆序对)]]