【数据结构】树状数组
你干嘛,并查集之后,就到了树形数据结构了,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)