C++树状数组详解

引入

如果给你n个数,然后进行q次询问,每次询问一个区间[x,y]的和,你会怎么做?
第一种方法:最简单的方法,用数组存起来,每次枚举x-y,ans加起来就可以,时间复杂度O(qn),十分慢。
第二种方法:或许大多数人会使用前缀和数组:sum[i]=a[1]+a[2]+…+a[i],所以求[x,y]只需要输出sum[y]-sum[x-1]即可,时间复杂度O(n),这是最快的方法之一了。


但是,如果加上一个条件:在q次询问中,有可能会临时使a[m]加上或减去一个数k(我们令这个为update(m,k)操作),也有可能会查询一个区间的和,怎么办呢?
如果还是用前缀和数组,就不方便了,因为update(m,k)需要更新sum[m]到sum[n]的值,于是时间复杂度又变为了O(qn)。
那么怎么办呢?于是有了树状数组。

树状数组

概念

树状数组,时间复杂度log级别的数据结构,且实现复杂度极小,不论是上面提到的update操作还是求前缀和。
这里写图片描述
如图,A数组是原始n个数的数组,C数组就是是树状数组(“树状”数组,是指一个普通数组,按树状存储,而不是一种STL中的数据结构)。

实现

观察一下有什么规律。

  • C[1] = A[1]
  • C[2] = C[1] + A[2] = A[1] + A[2]
  • C[3] = A[3]
  • C[4] = C[2] + C[3] +A[4] = A[1] + A[2] + A[3] + A[4]
  • C[5] = A[5]
  • C[6] = C[5] + A[6] = A[5] + A[6]
  • C[7] = A[7]
  • C[8] = C[4] + C[6] + C[7] + A[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8]

不难发现,好像和二进制很有关系。

但是很难再想下去,事实上是这样的:
定义lowbit(x)为x二进制下末尾0的个数。
则含有C[ i0 ]的C数组中的位置有:
i0
i1 = i0 + lowbit( i0 )
i2 = i1 + lowbit( i1 )
i3 = i2 + lowbit( i2 )
… …
ik = ik1 + lowbit( ik1 )
ikn
如果没法理解,写一个循环就懂了:

for(int i=x;i<=n;i+=lowbit(i))

计算lowbit

lowbit(x)=x&-x
为什么?这里复制了一篇证明(懒得打)

首先明白一个概念,计算机中-i=(i的取反+1),也就是i的补码
而lowbit,就是求(树状数组中)一个数二进制的1的最低位,例如01100110,lowbit=00000010;再例如01100000,lowbit=00100000。
所以若一个数(先考虑四位)的二进制为abcd,那么其取反为(1-a)(1-b)(1-c)(1-d),那么其补码为(1-a)(1-b)(1-c)(2-d)。
如果d为1,什么事都没有-_-|||但我们知道如果d为0,天理不容2Σ( ° △ °|||)︴
于是就要进位。如果c也为0,那么1-b又要加1,然后又有可能是1-a……直到碰见一个为补码为0的bit,我们假设这个bit的位置为x
这个时候可以发现:是不是x之前的bit的补码都与其自身不同?,x之后的补码与其自身一样都是0?
例如01101000,反码为10010111,补码为10011000,可以看到在原来数正数第五位前,补码的进位因第五位使其不会受到影响,于是0&1=0,;
但在这个原来数“1”后,所有零的补码都会因加1而进位,导致在这个“1”后所有数都变成0,再加上0&0=0,所以他们运算结果也都是零;
只有在这个数处,0+1=1,连锁反应停止,所以这个数就被确定啦O(∩_∩)O
所以and以后只有x这个bit是一……

update操作

当要动态改变一个数时,用刚刚的循环枚举出与它相关的位置,都增加(减少)即可:

void update(int k,int x)
{
    for(int i=k;i<=n;i+=lowbit(i))
        C[i]+=x;
}

getsum操作

就是求前缀和,同样的,倒着进行刚刚的循环,累加路上的值即可:

int getsum(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i))//i要大于0
        ans+=C[i];
    return ans;
}

关于代码风格

树状数组的update和getsum基本是通用的,建议不要自己改函数名,lowbit可以写函数,也可以宏定义:#define lowbit(x) (x&-x)

例题

Stars

  • 16
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值