『树状数组』树状数组模板

『树状数组』树状数组模板

竞赛需要用到的点

  • 树状数组可以解决大部分基于区间上更新、查询的操作
  • 树状数组能写的线段树都能写,但线段树能写的树状数组不一定能写
  • 代码量小,且常数比线段树小
  • 树状数组是 二进制 的混合使用
  • lowbit(x) -> x&(-x) 为何? -x = x的二进制数中 0 变 1,1 变 0 然后末尾 + 1 lowbit可以得到一个由原二进制数变来的只有末尾1的新的二进制数

树状数组略讲

此处借用 百度百科 的图片

由此图我们可以得到以下信息(所有信息均用二进制处理)

  • 所有区间所包含的点的数量为 \(2^k\) 其中 \(k\) 为该数二进制下末尾 \(0\) 的个数

  • [单点修改] 当我们修改一个点 \(i\) 的值后,那么所包含 \(i\) 的区间都要改变

    • 哪些值要变? \(i\) 在二进制下,加上该二进制下最低位1 例如(1001 -> 1010)(9 -> 10)
  • [单点查询] 因为树状数组存储的形式类似于前缀和的形式,所以单点查询的办法也显而易见

    • 如何查询?查询 \(b_i\)\(b_{i - 1}\) 的值,然后两式相减 可得 \(a_i\)
  • [区间查询] 和单点查询类似 若查询\([i, j]\) ,那么可得 \(b_j - b_{i - 1}\)

  • [区间更新] 对于这种问题我们分两种情况

    • [区间修改&单点查询] 这里我们需要用到差分的思想,即将原本的树状数组的数组用差分数组来表示 \(b_i = a_i - a_{i - 1}\) ,答案也呼之欲出,对于差分 我们每次想得到 \(a_n\) 都是 \(\Sigma_{i = 1}^{n}b_i\) 然后对于每次修改,我们对 \(b_i\)\(b_j + 1\) 进行单点修改即可。

    • [区间修改&区间查询] 这里我们也要用到差分的思想,但这次我们需要维护两个差分数组(\(sum1\)\(sum2\))为什么? 我们从上面可以得到,想要知道 \(a_{[i, j]}\) 的和就得用以下式子求出,\(\Sigma_{i = 1}^na = \Sigma_{i = 1}^n\Sigma_{j = 1}^ib_j\) 激动人心的化简时刻到了$$\begin{eqnarray}\Sigma_{i = 1}^na &=& \Sigma_{i = 1}^n\Sigma_{j = 1}^ib_j \&=& n\times b_1+(n-1)\times b_2 + (n - 2)\times b_3 + ... + b_n \&=& n\Sigma_{i = 1}^nb_i - b_2-2b_3-3b_4-...-(n-1)b_n\&=&n\Sigma_{i = 1}^nb_i + \Sigma_{i = 2}^n\Sigma_{j = 1}^{i - 1}(-b_i)\&=&n\Sigma_{i = 1}^nb_i + \Sigma_{i = 2}^n(-b_i)\times (i - 1)\&=&n\Sigma_{i = 1}^nb_i - \Sigma_{i = 2} ^ nb_i\times (i - 1)\end{eqnarray}$$

      因为 \(\Sigma_{i = 2}^nb_i\times (i - 1)\)\(\Sigma_{i = 1}^nb_i\times (i - 1)\) 等价
      所以原式子 = \(n\Sigma_{i = 1}^nb_i - \Sigma_{i = 1}^nb_i\times (i - 1)\) 在这个式子中,由于有\(\Sigma_{i = 1}^n\)所以我们可以易得用差分,用两个差分数组维护 \(b_i\)\(b_i \times (i - 1)\) 就可以了

部分模板代码展现

  • 单点修改 单点查询
#define fre yes

#include <cstdio>

const int N = 100005;
int a[N], b[N];
int n;

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

void point_change(int x, int k) { // point_change(x, k)
    while(x <= n) {
        b[x] += k;
        x += lowbit(x);
    }
}

int getSum(int x) { // getSum(x) - getSum(x - 1)
    int res = 0;
    while(x > 0) {
        res += b[x];
        x -= lowbit(x);
    } return res;
}

int main() {
    ...
}
  • 单点修改 区间查询
#define fre yes

#include <cstdio>

const int N = 100005;
int a[N], b[N];
int n;

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

void point_change(int x, int k) { // point_change(x, k)
    while(x <= n) {
        b[x] += k;
        x += lowbit(x);
    }
}

int getSum(int x) { // getSum(r) - getSum(l - 1)
    int res = 0;
    while(x > 0) {
        res += b[x];
        x -= lowbit(x);
    } return res;
}

int main() {
    ...
}
  • 区间修改 单点查询
#define fre yes

#include <cstdio>

const int N = 100005;
int a[N], b[N];
int n;

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

void interval_change(int x, int k) { // point_change(l, x) point_change(r + 1, -x)
    while(x <= n) {
        b[x] += k;
        x += lowbit(x);
    }
}

int getSum(int x) { // getSum(x)
    int res = 0;
    while(x > 0) {
        res += b[x];
        x -= lowbit(x);
    } return res;
}

int main() {
    ...
}
  • 区间查询 区间修改
#define fre yes

#include <cstdio> 

const int N = 100005;
int sum1[N], sum2[N];
int n;

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

void interval_change(int j, int k) { 
    // push -> interval_change(i, a[i] - a[i - 1])
    // change -> interval_change(l, k) interval_change(r + 1, -k)
    int i = j;
    while(j <= n) {
        sum1[j] += k;
        sum2[j] += k * (i - 1);
        j += lowbit(j);
    }
}

int getSum(int i) { // getSum(r) - getSum(l - 1)
    int res = 0, x = i;
    while(i > 0) {
        res += x * sum1[i] - sum2[i];
    	i -= lowbit(i);
    } return res;
}

int main() {
    ...
}
posted @ 2019-09-05 10:51  Nicoppa  阅读(1021)  评论(0编辑  收藏  举报