解锁算法竞赛秘籍:树状数组(Fenwick Tree)的魅力(先看动画)

树状数组(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(逆序对)]]

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值