【详解】树状数组
2018-02-09 15:11
141 查看
引入
如果给你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
的值,于是时间复杂度又变为了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 = ik−1 + lowbit(ik−1)
(ik≤n)
如果没法理解,写一个循环就懂了:
for(int i=x;i<=n;i+=lowbit(i))1
计算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&a
b8c1
mp;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; }1
2
3
4
5
getsum操作
就是求前缀和,同样的,倒着进行刚刚的循环,累加路上的值即可:int getsum(int x) { int ans=0; for(int i=x;i;i-=lowbit(i))//i要大于0 ans+=C[i]; return ans; }1
2
3
4
5
6
7
关于代码风格
树状数组的update和getsum基本是通用的,建议不要自己改函数名,lowbit可以写函数,也可以宏定义:#define lowbit(x) (x&-x)
例题
Stars相关文章推荐
- C语言树状数组的实例详解
- 【模板】树状数组(详解)
- 树状数组 详解
- 树状数组的改段求段详解
- POJ 1195 树状数组详解
- 【面试常见算法整理】Binary Indexed Tree(Fenwick Tree,树状数组)详解
- 树状数组详解
- 详解树状数组三种模型
- 详解karma & jasmine自动化测试
- 详解Android通过修改配置文件设置wifi密码
- Yii 2中的load()和save()示例详解
- js 编码escape()、encodeURI()、encodeURIComponent()区别详解
- android meta-data 应用详解
- Server-U FTP与AD完美集成方案详解
- strace详解及实战
- Protocol Buffer技术详解(Java实例)
- Dwr3.0与struts2一起使用的方法步骤详解
- Linux 常用命令详解链接
- 深入 Promise(一)——Promise 实现详解
- STL中map用法详解