你干嘛,并查集之后,就到了树形数据结构了,O(∩_∩)O哈哈~


河马~~~

  • 进入正题

树状数组

树状数组的特点是代码量小,常数小.但具有局限性,如处理区间最大值.

需要知道的是:
普通树状数组维护的信息及运算要满足 结合律可差分,如 加法(和)、乘法(积)、异或等。

  • 结合律 ( x ∘ y ) ∘ z = x ∘ ( y ∘ z ) (x \circ y) \circ z = x \circ (y \circ z) (xy)z=x(yz) ,其中 ∘ \circ 是一个二元运算符。

  • 可差分:具有逆运算的运算,即已知 x ∘ y x \circ y xy 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] [xlowbit(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,l1] 得到 [ 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] [xlowbit(x)+1,x] ,既然这段区间可以表示出来,那我们就要求右端点为 x − l o w b i t ( x ) x-lowbit(x) xlowbit(x) 的区间,又因为 c x − l o w b i t ( x ) c_{x-lowbit(x)} cxlowbit(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)] [xlowbit(x)lowbit(xlowbit(x)),xlowbit(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[xlowbit(x)+1x]
  • x ← x − lowbit ⁡ ( x ) x \gets x - \operatorname{lowbit}(x) xxlowbit(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 s2k+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 xy , 有 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=s2k+1+2k,x=s2k+1+b(b2k) ,那么假设 c x c_x cx c y c_y cy 相交.那么有 l y ≤ x l_y\le x lyx .又因为 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=s2k+1+blowbit(b)+1ly , 故 l y ≤ l x ≤ x ≤ y l_y\le l_x \le x \le y lylxxy ,证毕.

  • 性质 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=s2k+1+2k,lx=s2k+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+1lowbit((s+1)2k+1)+1lx.证毕.

  • 性质 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=s2k+1+2k,y=x+b(b<2k) ,则 l y ≤ x l_y\le x lyx ,而 l y = x + b − l o w b i t ( b ) + 1 > x l_y=x+b-lowbit(b)+1>x ly=x+blowbit(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=aiai1 , 那么 ∑ 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=1rj=1idi
可以看出 d k d_k dk 会出现 ( r − k + 1 ) (r-k+1) (rk+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=1rdii .
这样我们只需用两个树状数组分别维护 d i d_i di 以及 d i ∗ i d_i*i dii 即可.
对于区间加来说, 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 vall ,在第 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+1x+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 xx+2i s u m ← s u m + t \mathrm{sum} \gets \mathrm{sum} + t sumsum+t ;否则扩展失败,不操作。

这样得到的 x x x 是满足 [ 1 … x ] [1 \ldots x] [1x] 前缀和 < k < k <k 的最大值,所以最终 x + 1 x + 1 x+1 就是答案。

看起来这种方法时间效率没有任何改善,但事实上,查询 [ x + 1 … x + 2 i ] [x + 1 \ldots x + 2^i] [x+1x+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+1x+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=1x1bi .( 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 次来出题.

Logo

集算法之大成!助力oier实现梦想!

更多推荐