
【数据结构】树状数组
树状数组学习笔记
你干嘛,并查集之后,就到了树形数据结构了,O(∩_∩)O哈哈~
河马~~~
- 进入正题
树状数组
树状数组的特点是代码量小,常数小.但具有局限性,如处理区间最大值.
需要知道的是:
普通树状数组维护的信息及运算要满足 结合律 且 可差分,如 加法(和)、乘法(积)、异或等。
-
结合律: ( x ∘ y ) ∘ z = x ∘ ( y ∘ z ) (x \circ y) \circ z = x \circ (y \circ z) (x∘y)∘z=x∘(y∘z) ,其中 ∘ \circ ∘ 是一个二元运算符。
-
可差分:具有逆运算的运算,即已知 x ∘ y x \circ y x∘y 和 x x x 可以求出 y y y 。
在树状数组中,以 c c c 数组表示树状数组,且每个节点管辖特定的区间,那么在树状数组中, c x c_x cx 管理的区间为 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [x−lowbit(x)+1,x] , 长度为 l o w b i t ( x ) lowbit(x) lowbit(x).
//lowbit 运算
int lowbit(int x) {
return x & (-x); // 利用补码性质
}
既然,每个节点有了特定的管辖区域,那么我们就可以解决区间查询的问题了.
区间查询
众所周知,我们可以用 [ 1 , r ] − [ 1 , l − 1 ] [1,r] - [1,l-1] [1,r]−[1,l−1] 得到 [ l , r ] [l,r] [l,r].
于是区间问题就变成了前缀和问题了.
那前缀和问题与该如何求解呢?
设想我们现在要求 [ 1 , x ] [1,x] [1,x] ,我们是否可以将这一段区间分成几个小区间来求解?是的,我们可以.回顾 c x c_x cx 表示的区间 [ x − l o w b i t ( x ) + 1 , x ] [x-lowbit(x)+1,x] [x−lowbit(x)+1,x] ,既然这段区间可以表示出来,那我们就要求右端点为 x − l o w b i t ( x ) x-lowbit(x) x−lowbit(x) 的区间,又因为 c x − l o w b i t ( x ) c_{x-lowbit(x)} cx−lowbit(x) 可以表示区间 [ x − l o w b i t ( x ) − l o w b i t ( x − l o w b i t ( x ) ) , x − l o w b i t ( x ) ] [x-lowbit(x)-lowbit(x-lowbit(x)),x-lowbit(x)] [x−lowbit(x)−lowbit(x−lowbit(x)),x−lowbit(x)]…以此类推即可覆盖区间 [ 1 , x ] [1,x] [1,x]
具体的,我们有:
- 从 c [ x ] c[x] c[x] 开始往前跳,有 c [ x ] c[x] c[x] 管辖 a [ x − lowbit ( x ) + 1 … x ] a[x-\operatorname{lowbit}(x)+1 \ldots x] a[x−lowbit(x)+1…x] ;
- 令 x ← x − lowbit ( x ) x \gets x - \operatorname{lowbit}(x) x←x−lowbit(x) ,如果 x = 0 x = 0 x=0 说明已经跳到尽头了,终止循环;否则回到第一步。
- 将跳到的 c c c 合并(记录答案)。
int query ( int x ){
int ans = 0 ;
for ( ; x ; x -= lowbit ( x ) ){
ans += c[x] ;
}
}
单点修改
等等,你应该还没了解树状数组的树形结构吧(树状也,怎可不见矣?)
那么我们先了解一下树状数组的一些性质吧.
我们记 l x l_x lx 表示 c x c_x cx 的区间左端点.再者我们知道一个数 x x x 可以表示为 s ∗ 2 k + 1 + 2 k s*2^{k+1}+2^k s∗2k+1+2k , 则 l o w b i t ( x ) = 2 k lowbit(x) = 2^k lowbit(x)=2k
- 性质 1 1 1 :当 x ≤ y x \le y x≤y , 有 c x c_x cx 与 c y c_y cy 不交或包含
证明:设 y = s ∗ 2 k + 1 + 2 k , x = s ∗ 2 k + 1 + b ( b ≤ 2 k ) y=s*2^{k+1}+2^k,x=s*2^{k+1}+b(b\le 2^k) y=s∗2k+1+2k,x=s∗2k+1+b(b≤2k) ,那么假设 c x c_x cx 与 c y c_y cy 相交.那么有 l y ≤ x l_y\le x ly≤x .又因为 l x = s ∗ 2 k + 1 + b − l o w b i t ( b ) + 1 ≥ l y l_x=s*2^{k+1}+b-lowbit(b)+1\ge l_y lx=s∗2k+1+b−lowbit(b)+1≥ly , 故 l y ≤ l x ≤ x ≤ y l_y\le l_x \le x \le y ly≤lx≤x≤y ,证毕.
- 性质 2 2 2 : c x c_x cx 真包含于 c x + l o w b i t ( x ) c_{x+lowbit(x)} cx+lowbit(x)
证明:设 x = s ∗ 2 k + 1 + 2 k , l x = s ∗ 2 k + 1 + 1 x=s*2^{k+1}+2^k,l_x=s*2^{k+1}+1 x=s∗2k+1+2k,lx=s∗2k+1+1,记 y = x + l o w b i t ( x ) y=x+lowbit(x) y=x+lowbit(x) ,有 l y = ( s + 1 ) ∗ 2 k + 1 − l o w b i t ( ( s + 1 ) ∗ 2 k + 1 ) + 1 ≤ l x l_y=(s+1)*2^{k+1}-lowbit((s+1)*2^{k+1})+1\le l_x ly=(s+1)∗2k+1−lowbit((s+1)∗2k+1)+1≤lx.证毕.
- 性质 3 3 3 : 对于 x < y < x + l o w b i t ( x ) x<y<x+lowbit(x) x<y<x+lowbit(x) 有 c x c_x cx 与 c y c_y cy 不交.
证明:若 c x c_x cx 与 c y c_y cy 交, 设 x = s ∗ 2 k + 1 + 2 k , y = x + b ( b < 2 k ) x=s*2^{k+1}+2^k,y=x+b(b<2^k) x=s∗2k+1+2k,y=x+b(b<2k) ,则 l y ≤ x l_y\le x ly≤x ,而 l y = x + b − l o w b i t ( b ) + 1 > x l_y=x+b-lowbit(b)+1>x ly=x+b−lowbit(b)+1>x,矛盾.证毕.
有了上边的性质,就可以观察树状数组了.(到时可能会放图)
对于单点修改 a x a_x ax ,发现只有管辖区间有 x x x 的 c c c 数组才会被更新(显然),结合性质 1 1 1 ,我们发现若 c y c_y cy 包含 x x x ,则一定包含 c x c_x cx , 又在形态上 c y c_y cy 是 c x c_x cx 的祖先,因此我们可以从 x x x 开始不断跳父亲,直到跳得超过了原数组长度为止。Code:
void updata ( int x , int val ){
for ( ; x <= n; x += lowbit(x) ){
c[x] += val ;
}
}
树状数组 1:单点修改,区间查询
树状数组 2:区间修改,单点查询
提示:差分数组的前缀和 Code
区间加及区间和
该问题可以使用两个树状数组维护差分数组解决。
具体的,设 d i = a i − a i − 1 d_i=a_i-a_{i-1} di=ai−ai−1 , 那么 ∑ i = 1 r a i = ∑ i = 1 r ∑ j = 1 i d i \sum_{i=1}^r a_i = \sum_{i=1}^r\sum_{j=1}^{i}d_i ∑i=1rai=∑i=1r∑j=1idi
可以看出 d k d_k dk 会出现 ( r − k + 1 ) (r-k+1) (r−k+1) 次,于是乎式子变成了这样:
∑ i = 1 r d i ∗ ( r + 1 ) − ∑ i = 1 r d i ∗ i \sum_{i=1}^rd_i*(r+1)-\sum_{i=1}^rd_i*i ∑i=1rdi∗(r+1)−∑i=1rdi∗i .
这样我们只需用两个树状数组分别维护 d i d_i di 以及 d i ∗ i d_i*i di∗i 即可.
对于区间加来说, d l d_l dl 增加 v a l val val , d r + 1 d_{r+1} dr+1 减少 v a l val val , 而对于第二个树状数组来说,在第 l l l 节点上增加 v a l ∗ l val*l val∗l ,在第 r + 1 r+1 r+1 节点上减少 v a l ∗ ( r + 1 ) val*(r+1) val∗(r+1).
LOJ#132. 树状数组 3 :区间修改,区间查询
二维树状数组
本蒟蒻还未掌握,到时再Update.
权值树状数组
首先先明白一个序列 a a a 的权值数组是什么
权值数组是一个一维数组,其中数组的下标对应原始序列中元素的值,数组元素的值表示原始序列中该下标值出现的次数。
离散化处理:当元素值范围较大时,可先离散化原始序列,再构建权值数组以压缩空间.
栗子就不举了.(逃)
而事实上,权值树状数组就是在权值数组上构建树状数组.他可以解决一些经典问题.
单点修改,查询全局第 k k k 小
可以考虑在权值树状数组上进行二分,每次都要 O ( l o g N ) O(log N) O(logN) 查询区间和,所以时间复杂度为 O ( l o g 2 N ) O(log^2 N) O(log2N) .时间都去哪了?
由于区间和耗费太多时间,于是聪明的你想的了倍增.具体的:
设 x = 0 x = 0 x=0 , s u m = 0 \mathrm{sum} = 0 sum=0 ,枚举 i i i 从 l o g 2 n log_2n log2n 降为 0 0 0 :
- 查询权值数组中 [ x + 1 … x + 2 i ] [x + 1 \ldots x + 2^i] [x+1…x+2i] 的区间和 t t t 。
- 如果 s u m + t < k \mathrm{sum} + t < k sum+t<k ,扩展成功, x ← x + 2 i x \gets x + 2^i x←x+2i , s u m ← s u m + t \mathrm{sum} \gets \mathrm{sum} + t sum←sum+t ;否则扩展失败,不操作。
这样得到的 x x x 是满足 [ 1 … x ] [1 \ldots x] [1…x] 前缀和 < k < k <k 的最大值,所以最终 x + 1 x + 1 x+1 就是答案。
看起来这种方法时间效率没有任何改善,但事实上,查询 [ x + 1 … x + 2 i ] [x + 1 \ldots x + 2^i] [x+1…x+2i] 的区间和只需访问 c [ x + 2 i ] c[x + 2^i] c[x+2i] 的值即可。
原因很简单,考虑 lowbit ( x + 2 i ) \operatorname{lowbit}(x + 2^i) lowbit(x+2i) ,它一定是 2 i 2^i 2i ,因为 x x x 之前只累加过 2 j 2^j 2j 满足 j > i j > i j>i 。因此 c [ x + 2 i ] c[x + 2^i] c[x+2i] 表示的区间就是 [ x + 1 … x + 2 i ] [x + 1 \ldots x + 2^i] [x+1…x+2i] 。
如此一来,时间复杂度降低为 Θ ( log n ) \Theta(\log n) Θ(logn) 。
P1138 第 k 小整数
这道题说实话用树状数组太不实际了,但很有生活可以练练手.
struct TreeArray{
int c[N] ;
void updata ( int x , int val ){
for ( ; x <= 30000 ; x += lowbit( x ) ){
c[x] += val ;
}
}
int query ( int x ){
int ans = 0 ;
for ( ; x ; x -= lowbit(x ) ){
ans += c[x] ;
}
return ans ;
}
int ksmall ( int x ){
int sum = 0 , k = 0 ;
for ( int i = log2(30000) ; i >= 0 ; --i ){
k += (1ll << i) ;
if ( k > 30000 || sum + c[k] >= x ){
k -= (1ll << i ) ;
}else sum += c[k] ;
}
return k + 1 ;
}
}t;
全局逆序对
这个需要思考,我们可以从大到小依次将每个数扔进权值树状数组中进行维护,设这个数为 x x x , 则我们将答案加上 ∑ i = 1 x − 1 b i \sum_{i=1}^{x-1}b_i ∑i=1x−1bi .( b i b_i bi 为数 i i i 出现的次数),正确性很显然.
这里就不写模板了,直接上一道题.
P1966 [NOIP 2013 提高组] 火柴排队
提示:将计算公式展开,再利用排序不等式,可以知道答案就是以 a i a_i ai 为第一关键字, b i b_i bi 跟随 a i a_i ai 排序后的位置的逆序对个数.(可能有点绕).代码.
对于本蒟蒻,学到这里是个大概了,还有一些技巧以及高级维护操作本蒟蒻还没有学会,到时应该会Update的.
习题
P3605 [USACO17JAN] Promotion Counting P
我们记答案数组为 a n s ans ans , 那么 a n s x = ans_x = ansx= 比 x x x 强的下属个数.而当你 D F S DFS DFS 的时候,你会记录之前的信息,不过没有关系, a n s x = 进入子树后的信息 − 之前没有进入子树时的信息 ans_x = 进入子树后的信息 - 之前没有进入子树时的信息 ansx=进入子树后的信息−之前没有进入子树时的信息 ,代码/记录.
P1972 [SDOI2009] HH的项链
先将 l i , r i l_i,r_i li,ri 以 r r r 为第一关键字从小到大排序,我们依次遍历,当碰到一个数 x x x 时,检查在树状数组中是否维护了 x x x , 若有则消除之前的维护,并添加新的维护,从而达到目的.(估计还是太抽象了)代码
P4113 [HEOI2012] 采花
跟上一道题十分类似,这里只做些许提示:维护每个元素最后以及倒数第二次出现的位置.
代码.
好像可以把出现两次扩展到出现 k k k 次来出题.
更多推荐
所有评论(0)