感觉是个好大的坑wa…我就简略地写写好了…其实树状数组在大部分时候仅作为线段树的替代品(执行的基本操作相似,且代码简单易懂,时空复杂度均低于线段树),那那那为什么需要线段树呢?树状数组的适用范围小啊(很小很小啊)执行基本修改查询操作已经很不错了还苛求什么呢…
概念与基本性质
树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
——以上内容摘自百度百科
举个栗子:
设原数组为A,树状数组为C,则
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
…
C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16
观察下图:
我们可以发现,i 号节点管辖的范围是 2 ^ k(k 是 i 的二进制末尾 0 的个数),区间最后一个元素必然是 Ai
显然,Cn=A(n – 2 ^ k + 1) + … + An
那么,问题来了:
如何求出 2 ^ k 的值呢?
这时需要引进一个特殊的函数 lowbit( )…lowb…it..( 卡在奇怪的地方.jpg )
1 | int lowbit(int x){ |
这个函数 巧妙 地利用了机器补码的特性,计算出了 x 二进制最低位 1 所代表的值,即 2 ^ k 的值。
然后我们就可以借助它 偷税 愉悦地完成接下来的操作了!!
执行操作
单点修改,区间查询
最基础的树状数组
代码实现
1 |
|
例题
区间修改,单点查询
其实树状数组本身并不支持区间修改,但是运用 人类智慧 我们可以发现通过差分数组(d[ i ] = a[ i ] - a[ i - 1 ] , a[ 0 ] = 0)可以将问题转换为单点修改,区间查询
代码实现
1 |
|
例题
区间修改,区间查询
这是树状数组最常用的操作,也是用线段树最难打的(怕是常用的原因)
我们参考区间修改,单点查询的操作,只要求出val()函数的前缀和,就可以实现复杂度为 O( log n )的区间查询了
设原数组为 a,d 为 a 的差分数组,欲求 a 的前缀和
a[ 1 ] + a[ 2 ] + a[ 3 ] + … + a[ n ]
= (d[ 1 ]) + (d[ 1 ] + d[ 2 ]) + (d[ 1 ] + d[ 2 ] + d[ 3 ]) + … +(d[ 1 ] + d[ 2 ] + … + d[ n ])
= d[ 1 ] · n + d[ 2 ] · (n - 1) + … +d[ n ]
= n · (d[ 1 ] + d[ 2 ] + … +d[ n ]) - (0 · d[ 1 ] + 1 · d[ 2 ] + … + (n - 1) · d[ n ])
所以我们还要再维护一个 d2,使 d2[ i ] = (i - 1) · d[ i ],每当修改 d 时就修改一下 d2,复杂度就不会改变
代码实现
就不用什么注释了…吧?
1 |
|
例题
CodeVS 1082 线段树练习 3
为什么难度是大师啊喂
二维树状数组
当然,树状数组这么有用(代码量小)的数据结构,怎么会少了矩阵呢?为什么我要新开一个标题啊…
在一维树状数组中,tree[ x ] 记录的是右端点为 x、长度为 lowbit( x ) 的区间的区间和。
那么在二维树状数组中,可以类似地定义 tree[ x ][ y ] 记录的是右下角为 (x, y),高为 lowbit( x ), 宽为 lowbit( y ) 的区间的区间和。
嘛,具体操作什么的(我好懒啊)前两种很好懂啊… 自己找度娘吧
至于区间修改 + 区间查询,可以参考我写的 【题解】上帝造题的七分钟(二维树状数组)
那…就写到这吧。