【进阶】树状数组 BIT

之前我讲过最基本的树状数组 ,这里讲一讲它的”进阶形态“.

具有可加性的内容让树状数组来维护会显得很方便(比如说和),而不满足可加性的内容让树状数组来维护会有点麻烦(虽然已经有 dalao 给出了树状数组求最大值和 select 的程序,但我想这个时候还是老老实实写线段树或者平衡树吧)。

之前讲的是单点修改加区间查询,现在就从树状数组区间修改单点查询开始吧。

先丢一波单点修改区间查询的代码。

class BinaryIndexedTree {
private:
    long long c[N];
    inline int lowbit(int x) { return x & (-x); }
    inline long long query(int x) {
        long long ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) 
            ans += c[i];
        return ans;
    }
public:
    inline void update(int x, long long v) {
        for (int i = x; i <= n; i += lowbit(i)) 
            c[i] += v;
    }
    inline long long query(int l, int r) { 
        return query(r) - query(l - 1); 
    }
} BIT;

本来我想把它装在 namespace 里面的,但是好像把一个 query 放在 class 的 private 里面会保险得多,防止主程序中调用了出锅~


好了,回到区间修改区间查询,相信大多数人都会,因为这两个东西是配套学的,就是运用查分的思想,没什么好说的,直接上代码。

class BinaryIndexedTree {
private:
    long long c[N];
    inline long long lowbit(long long x) { return x & (-x); }
    inline void update(int x, long long v) {
        for (int i = x; i <= n; i += lowbit(i)) 
            c[i] += v;
    }
public:
    inline long long query(int x) {
        long long ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) 
            ans += c[i];
        return ans;
    }
    inline void update(int l, int r, long long v) {
        update(l, v);
        update(r + 1, -v);
    }
};

树状数组维护的操作只能是一个单点一个区间吗?No!它可以支持区间修改区间查询哦!

但是,这首先还是要用到差分,然后来推式子~
∑ i = 1 n a i = ∑ i = 1 n ∑ j = 1 i c j = ∑ j = 1 n ∑ i = j n c j = ∑ i = 1 n ∑ j = i n c i = ∑ i = 1 n ( n − i + 1 ) c i = ( n + 1 ) ∑ i = 1 n c i − ∑ i = 1 n i c i \begin{aligned} \sum_{i = 1}^n a_i &amp; = \sum_{i=1}^n\sum_{j=1}^ic_j \\ &amp; = \sum_{j=1}^n\sum_{i=j}^nc_j \\ &amp; = \sum_{i=1}^n\sum_{j=i}^nc_i \\ &amp; = \sum_{i=1}^n(n-i+1)c_i\\ &amp; = (n + 1)\sum_{i=1}^nc_i - \sum_{i=1}^nic_i \end{aligned} i=1nai=i=1nj=1icj=j=1ni=jncj=i=1nj=inci=i=1n(ni+1)ci=(n+1)i=1ncii=1nici
以上式子真心建议拿起笔来算一算,因为这个涉及到基础的求和符号及其运算(当然聪明的你肯定能从第一步直接跳到倒数第二步,虽然如此但我还是建议用上面的方法算一算,毕竟每一步都有运算定律支撑,是相当严谨的)。

这下可以看出来了我们该如何操作了:用一棵树状数组维护 c i c_i ci ,用另外一棵树状数组维护 i c i ic_i ici 。问题得到了解决。

class BinaryIndexedTree {
private:
    long long c[2][N];
    inline int lowbit(int x) { return x & (-x); }
    inline void update(int x, long long v) {
        for (int i = x; i <= n; i += lowbit(i)) 
            c[0][i] += v, c[1][i] += x * v;
    }
    inline long long query(int x) {
        long long ans = 0;
        for (int i = x; i > 0; i -= lowbit(i)) 
            ans += (x + 1) * c[0][i] - c[1][i];
        return ans;
    }

public:
    inline void update(int l, int r, long long v) {
        update(l, v);
        update(r + 1, -v);
    }
    inline long long query(int l, int r) { 
        return query(r) - query(l - 1); 
    }
};

接下来是拓展到高维的时候了,同学们只要编过就知道,树状数组拓展到高维的代码量几乎是所有可拓展的数据结构中最简单的。当然前两种操作我就不再赘述了,其实这两个操作比起讲还是看代码来得轻松(前置技能是二维前缀和,其实就是容斥原理,实在不懂就画个图)。

二维树状数组单点修改区间查询·

class BinaryIndexedTree2D {
private:
    long long c[N][N];
    inline int lowbit(int x) { return x & -x; }
    inline long long query(int x, int y) {
        long long ans = 0;
        for (int i = x; i > 0; i -= lowbit(i))
            for (int j = y; j > 0; j -= lowbit(j))
                ans += c[i][j];
        return ans;
    }
public:
    inline void update(int x, int y, int v) {
        for (int i = x; i <= n; i += lowbit(i))
            for (int j = y; j <= m; j += lowbit(j)) 
                c[i][j] += v;
    }
    inline long long query(int x, int y, int z, int w) {
        return query(z, w) - query(z, y - 1) - query(x - 1, w) + query(x - 1, y - 1);
    }
};

二维树状数组区间修改单点查询(额,好像没有这种东西吧。。。有的话类比一下)


二维树状数组区间修改区间查询

不得不说是恶心呐(推式子比较繁琐),但是相较写个二维线段树已经是相当轻松了。

这回使用之前的方法肯定是可行的,但是我太懒了,就跳个几步(饶了我吧),虽然我知道你们都不用想那被省略的几步就可以推得出来
∑ i = 1 n ∑ j = 1 n a j = ∑ i = 1 n ∑ j = 1 n ( n − i + 1 ) ( m − j + 1 ) c j = ∑ i = 1 n ∑ j = 1 n [ ( n + 1 ) ( m + 1 ) − ( m + 1 ) i − ( n + 1 ) j + i j ] c i j = ( n + 1 ) ( m + 1 ) ∑ i = 1 n ∑ j = 1 n c i j − ( m + 1 ) ∑ i = 1 n ∑ j = 1 n i c i , j − ( n + 1 ) ∑ i = 1 n ∑ j = 1 n j c i j + ∑ i = 1 n ∑ j = 1 n i j c i j \begin{aligned} \sum_{i = 1}^n\sum_{j=1}^n a_{j} &amp; = \sum_{i=1}^n\sum_{j=1}^n(n-i+1)(m-j+1)c_j \\ &amp; = \sum_{i=1}^n\sum_{j=1}^n[(n+1)(m+1)-(m+1)i-(n+1)j+ij]c_{ij} \\ &amp; = (n+1)(m+1)\sum_{i=1}^n\sum_{j=1}^nc_{ij}-(m+1)\sum_{i=1}^n\sum_{j=1}^nic_{i,j}-(n+1)\sum_{i=1}^n\sum_{j=1}^njc_{ij}+\sum_{i=1}^n\sum_{j=1}^nijc_{ij} \end{aligned} i=1nj=1naj=i=1nj=1n(ni+1)(mj+1)cj=i=1nj=1n[(n+1)(m+1)(m+1)i(n+1)j+ij]cij=(n+1)(m+1)i=1nj=1ncij(m+1)i=1nj=1nici,j(n+1)i=1nj=1njcij+i=1nj=1nijcij
这下明白了,需要建四棵树状数组,分别维护双 ∑ \sum 里面的信息。

class BinaryIndexedTree2D {
private:
    long long c[4][N][N];
    inline int lowbit(int x) { return x & (-x); }
    inline void update(int x, int y, long long v) {
        for (int i = x; i <= n; i += lowbit(i))
            for (int j = y; j <= m; j += lowbit(j))
                c[0][i][j] += v, c[1][i][j] += x * v, c[2][i][j] += y * v, c[3][i][j] += x * y * v;
    }
    inline long long query(int x, int y) {
        long long ans = 0;
        for (int i = x; i > 0; i -= lowbit(i))
            for (int j = y; j > 0; j -= lowbit(j))
                ans += (x + 1) * (y + 1) * c[0][i][j] - (y + 1) * c[1][i][j] - (x + 1) * c[2][i][j] + c[3][i][j];
        return ans;
    }
public:
    inline void update(int x, int y, int z, int w, long long v) {
        update(x, y, v);
        update(x, w + 1, -v);
        update(z + 1, y, -v);
        update(z + 1, w + 1, v);
    }
    inline long long query(int x, int y, int z, int w) {
        return query(z, w) - query(x - 1, w) - query(z, y - 1) + query(x - 1, y - 1);
    }
};

式子比较长,就容忍一下吧,还是不得不说,这么写比写两重线段树好受多了。

P.S.不知道什么时候 C S D N CSDN CSDN 支持 K a t e x Katex Katex 了,但是为什么 K a t e x Katex Katex 不认识 a l i g n align align 呢???但实话实说 K a t e x Katex Katex 是真的快。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值