[模板][数据结构] 树状数组

树状数组及各种操作

同样是先挖坑再慢慢填

首先要知道一个贯穿树状数组始终的操作:lowbit

int lowbit(int x){
    return x & (-x);
}

这个函数的意思是:返回 \(x\) 的二进制表达中第一个 \(1\) 的位置(以 \(2^i\) 的形式表示)

如何做到的:因为整数是以补码表示的,

举个例子:

\((2)_{dec} = (00000010)_{bin}\)

$ (-2)_{dec} = (11111110)_{bin} $

​ 所以 lowbit(2) = 2

而这个 lowbit 值恰好表示了树状数组上一个点所包含的区间

以树状数组为c[],原数组为a[]为例:

c[1] = a[1]

c[2] = a[1] + a[2]

c[3] = a[3]

c[4] = a[1] + a[2] + a[3] + a[4]

以此类推。

这样我们就有了树状数组的基本结构。


单点修改 + 区间求和

还是举例子:

​ 操作:将 a[1] 加上 \(5\)

​ 那么管理它的每一个位置都要加上 \(5\)

​ 即 c[1]c[2]c[4]……… 都要加上 \(5\)

这时我们会发现每个要更新的数组恰恰为前一个数组的位置加上lowbit值。

于是有以下代码:

void add(int val, int pos){
    while(pos <= n){
        c[pos] += val;
        pos += lowbit(pos);
    }
} // 单点加

至于单点修改为某值仅需要略微修改,不再赘述。

而区间求和自然就不难了,只需逆向进行上面的操作(然后作个差):

int getsum(int pos){
    int ans = 0;
    while(pos > 0){
        ans += c[pos];
        pos -= lowbit(pos);
    }
    return ans;
} // 求 1 ~ pos 之和 

区间修改 + 单点求和

(这个其实不常用,一般用区间求和版本的比较多)

将树状数组维护的数组建为差分数组,这样修改区间值的时候实际只需要在差分数组的 \(l\)\(r+1\) 位置操作。

注意此时的 getsum 返回的是询问的 pos 的值。

void rangeAdd(int l, int r, int val){
    add(l, val); add(r+1, -val);
    return;
}
LL getsum(int pos){
    LL ans = 0;
    while(pos > 0){
        ans += d[pos];
        pos -= lowbit(pos);
    }
    return ans;
}

此时的 add 函数没有变化。

posted @ 2020-06-01 06:47  ChPu437  阅读(136)  评论(0编辑  收藏  举报