【算法】浅谈树状数组

参考资料

    从0到inf,超详细的树状数组详解


概念

    从名字就不难看出,树状数组就是一个树形的数组,如下图

    

    其中:

    01 02 03 04 为原数组,记为 a 数组

    1 2 3 4 为树状数组,记为 t 数组

    不难发现,

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

    但似乎还是找不到规律,我们尝试将它转换成二进制试试

  t[1]   = a[1]
  t[10]  = a[1] + a[10]
  t[11]  = a[11]
  t[100] = a[1] + a[10] + a[11] + a[100]

    也就是说,从最低位的 1 开始一直加到 1 ,就是 t[i] 的值,即

\[t[i] = a[i]+……a[i-2^k+2]+a[i-2^k+1] \]

    这时引入 lowbit 的概念,它可以计寻找出最低位的 1

\[lowbit(x) = x \And (x-1) \]

    为什么这样就能取到最低位的 1 了呢?

    这里先给出一些基本概念,方便不熟悉的读者观看:二进制的原码、反码、补码

    我们直接以 7 为例:

    7 的二进制为 0111

    -7 的二进制为 1001

    我们又知道 & 表示:如果两个相应的二进制位都为1,则该位的结果值为1,否则为0

    所以 \(7\And(-7)=0001\),就得出了最低位 1

    怎么将这个结论具有普遍性呢?这里不妨自己尝试一下


实现

    根据上面的分析,我们不难得出查询区间和 (l,r) ,只需利用前缀和即可

  int query(int x){
  	  int sum=0;
	  for(int i=x;i>=1;i-=lowbit(i)) sum+=t[i];
	  return sum;
  }

    更改时,由于树状数组存储的是前缀和,所以只需将它上面的所有父节点同时进行更改即可

  void update(int x,int k){
	  for(int i=x;i<=n;i+=lowbit(i)) t[i]+=k;
  }

    • 单点修改和区间查询

        例题:树状数组1

        更改时只需:\(update(x,k)\)

        查询时只需输出:\(query(r)-query(l-1)\)

        将输入的数组看做更改操作即可


    • 区间修改与单点查询

        例题:树状数组2

        这时候我们不能沿用上面那道题的思想了,需要依靠差分的思想来解决

        根据差分数组的特性,我们可以得到以下几个规律:

    差分数组的前缀和可以得到原来数组的值
    将原数组 (l,r) 加上 k 等价于将差分数组第 l 项 +k, 第 r 项 -k

        证明这两个规律可以通过举例子的方式

        这样我们进行区间修改时只需:\(update(l,k),update(r,-k)\)

        查询时只需输出:\(query(x)\)

        注意这里都是对差分数组操作


    • 区间修改和区间查询

        \(\tiny\text{【为什么不试试线段树呢】}\)

        例题:线段树1

        这时我们发现,如果根据上面的思想,区间修改对差分数组操作,区间查询对原数组操作,肯定是不行的。

        我们考虑化简区间查询 (l,r) 的式子:

        \(\sum\limits_{i=l}^ra_i = t[l]+t[l]+t[l+1]+……t[l]+t[l+1]+……t[r]\)

        \(\qquad \;=n \times t[l] + (n-1) \times t[l+1]+…… 1 \times t[r]\)

        \(\qquad \;=\sum\limits_{i=1}^n(n-i+1)\times t[i]\)

        \(\qquad \;=n\times \sum\limits_{i=1}^nt[i]-\sum\limits_{i=1}^nt[i]\times(i-1)\)

        这样我们可以维护两个数组,一个数组 t[i] 来维护差分,一个数组 pr[i] 维护 \(t[i]\times (i-1)\)

        更改时除了之前的基本操作,还要更新 pr[i],一个简单的乘法分配律可得:\(pr[i]+=k\times (x-1);\)

    查询时按照我们之前化简出来的式子计算即可

posted @ 2022-05-03 11:40  Cloote  阅读(53)  评论(0编辑  收藏  举报