【进阶】树状数组 BIT

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

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

以前讲的是单点修改加区间查询,如今就从树状数组区间修改单点查询开始吧。web

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

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 里面会保险得多,防止主程序中调用了出锅~数据结构


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

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!它能够支持区间修改区间查询哦!svg

可是,这首先仍是要用到差分,而后来推式子~
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}
以上式子真心建议拿起笔来算一算,由于这个涉及到基础的求和符号及其运算(固然聪明的你确定能从第一步直接跳到倒数第二步,虽然如此但我仍是建议用上面的方法算一算,毕竟每一步都有运算定律支撑,是至关严谨的)。spa

这下能够看出来了咱们该如何操做了:用一棵树状数组维护 c i c_i ,用另一棵树状数组维护 i c i ic_i 。问题获得了解决。.net

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); 
    }
};

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

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

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}
这下明白了,须要建四棵树状数组,分别维护双 \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 支持 K a t e x Katex 了,可是为何 K a t e x Katex 不认识 a l i g n align 呢???但实话实说 K a t e x Katex 是真的快。。