树状数组 (Binary Indexed Tree)

思路介绍

树状数组(Binary Indexed Tree)可以高效的维护和查询前缀和或者区间和

如果数组是静态的,那么很好处理,我们只需要预处理前缀和即可,复杂度是O(n)

但如果这个数组是动态的,那么将会变得发杂起来

我们不可能对每一次变化都进行前缀和操作,这样时间复杂度过高,所以我们采用更高级的方法实现

假设我们有如下的数组:

image-20221213162342632

有这样一个朴素的想法,主要思路是二分的想法:

即, 每两个数求一次和,那么求和的过程将会被缩减一半:

image-20221213162405102

接着这个思路继续,我们还可以继续对两两值求和,只到只剩下一个数为止。

image-20221213162420660

这样我们就可以操作很少的数得到一段区间的和

举个例子:如果我想知道1-5之间的和,我只需要读取第3行的10然后加上第一行的5得15

这样时间将大大减小,如果要修改某个值,也只需要更改包含该区间的和即可

 

优化

注意一点,在以上,我们有些数是永远用不到的,如下图:

image-20221213162814525

如果我们要求1-5的和,需要第三行的10以及第一行的5,求1-6的和,只需要第三行10,第二行11,

观察可知,每一行的偶数个都是不需要的。

删去后发现实际上剩下的数与原本的数一致

image-20221213163331501

 

维护与查询思路

我们知道了基本原理,那我们该如何获取我需要的区间和呢,具体的数组下标是什么

直接给出结论:

假设我们要查询区间1-7的和, 即sum(7);

我们对输入的数的二进制进行操作,每次去掉二进制下最后一个1

具体的看:

7的二进制是 111, 去掉最后的1 变成 110 ,即 6

之后再去掉变成 100, 即4

再去掉后就没了,所以1-7的区间和为: sum(7) = tree[7] + tree[6] + tree[4]; (tree数组即为上面将无用数据删除后形成的数组)

现在我们更改 a3

我们需要修改的下标同样也是对二进制进行操作,每次在最后的1上加1.

3的二进制为011, 加一为 100 即 4

再加 1000 即 8 ,以此类推直到达到边界

 

编码实现

在维护和查询思路中,其核心便是求二进制下最后一个1所代表的数

我们通过lowbit()函数获得这个数的最后一位1所代表的数。

二进制复习

代码及其精简,涉及二进制相关,下面是于此相关的二进制小总结(如果会二进制默认跳过)

这两篇博客写的很好可以看看:

位运算全面总结,关于位运算看这篇就够了unique_pursuit的博客-CSDN博客位运算

负数的二进制表示storm_fury的博客-CSDN博客负数二进制

二进制基本符号

符号描述运算规则实例
&两个位均为1的时候,结果才为11001&0101 = 0001, 0000 & 0001 = 0000
|两个位都为0的时候,结果才为00111|0000 = 0111, 0000 | 0000 = 0000
^异或两个位相同为0,个位不同为10101^0000 = 0101 , 0001^0001 = 0000
~取反按位取反,0变1,1变0~0001 = 1110, ~0000 = 1111
<<左移各二进制位左移若干位,高位丢弃,低位补00001<<k=0100,k=2,k kk是左移的位数,这里k = 2 k=2k=2
>>右移各二进位全部右移若干位,对无符号数,高位补0,有符号数,右移补10100>>k=0001,k=2,k kk是右移的位数,这里k = 2 k=2k=2

位运算常用玩法

特别要点出来的是相乘和相除的操作:

a<<1 == a*2

a > > 1 == a/2

a<< 1 | 1 == 2*a + 1

这两在二分算法中有着重要运用,相对于普通相除来说应对数据溢出有奇效

 

负数的二进制存储:

在计算机中, 正数是直接用原码表示的,如单字节5,在计算机中就表示为:0000 0101。 负数以其正值的补码形式表示,如单字节-5,在计算机中表示为:1111 1011。

 

负数的补码是由其正数的二进制原码转化而成

5 的原码为0000 0101, -5 的原码为 1000 0101,最前面的1是符号位,

储存的时候将符号位不变其他按位取反得: 1111 1010 ,最后加一得: 1111 1011;

 

lowbit的二进制解释

x & (-x)

我们可以知道负数在计算机中以补码的形式存储。

我们根据负数的存储条件,先去掉负数存储的最后一步,即加一操作

0100 1100 ---> 1011 0011, 此时我们进行&操作,得到的数是0,但应为最后加了一个一,这个一会不断前进到原码第一个一的位置

具体的情况那笔演示一下可以得出,所以最后&的结果便是可以得到最低为1代表的数

lowbit巧妙的运用了负数补码的性质。

 

代码解释

基础准备

lowbit函数

查询前缀和操作

更新数值操作

测试一下

树状数组基本应用

区间修改+单点查询

对于一个数组A={a1, a2, a3 .......}, 如果我们要对一段区间进行修改,最简单的方法是对这一整个区间的每一个数进行修改

但是这样的时间复杂度过高,会TLE,所以我们可以采用树状数组+差分数组的方式来提高效率

image-20221215170223961

一直如果想对一段区间整体加减,我们只需要对差分数组的两个点进行修改,这样就实现了将时间复杂度由O(N)转化为O(1)

同时如果想查询一个数的值,我们需要对差分数组进行求和,而求和这件事对于树状数组不是难事

所以我们可以将差分数组用树状数组来表示,这样就可以实现区间修改+单点查询

我们举个例子:hdu 1556

image-20221215171117204

代码

区间修改+区间查询

我们先定义两个数组, 一个是原数组A, 一个是差分数组 D

如果我们要想对区间进行修改,那么最好的办法便是通过差分数组,所以我们如过可以得到差分数组与原数组和的关系,我们就可以通过差分数组快速且高效的完成区间修改和区间查询。

推导过程如下, D为差分数组的值

a1+a2+a3+a4+.....+ak{D1+(D1+D2)+(D1+D2+D3)+....+(D1+D2+...+Dk)kD1+(k1)D1+(k2)D2+....+(k(k1))Dkk(D1+D2+D3+...+Dk)(D2+2D3+...+(k1)Dk)a1+a2+a3+a4+.....+ak=ki=1kDii=1k(i1)Di

所以我们可以用两个树状数组分别来实现两个的求和

一个来实现Di, 一个来实现 (i-1)Di

举个例子:洛谷P3372

image-20221215182541982

image-20221215182603670

 

二维区间修改 + 二维区间查询

我们通过之前的前缀和与差分知识可以知道:

差分是前缀和的逆运算,

即二维差分数组的定义是:D为差分数组, a为原数组

D[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]

代表的即是图中阴影部分。

image-20221229201058914

同过二维差分数组,我们也可以得到原数据

a[c][d]=i=1cj=1dd[i][j]

因为是二维求和,所以我们的树状数组也要变成二维的树状数组

同时update,sum函数都要改变

每次i行变化lowbit(i), j行变化lowbit(j)

如下:

update(二维)

sum(二维)

我们测试一下:(测试只是为了看的更清楚,属于题外话,可以跳过)

基本信息

辅助函数

开始

二维区间查询

我们可以推出区间查询与差分数组之间的关系

a[][](a,b)(c,d):i=acj=bda[i][j]=i=1cj=1da[i][j]i=1cj=1b1a[i][j]i=1a1j=1da[i][j]+i=1a1j=1b1a[i][j]i=1nj=1ma[i][j]a[i][j]=k=1il=1jD[k][l]i=1nj=1ma[i][j]=i=1nj=1mk=1il=1jD[k][l]D[k][l]D[1][1]xyD[1][2]x(y1)D[k][l](nk+1)(ml+1)i=1nj=1mk=1il=1jD[k][l]=i=1nj=1mD[i][j](ni+1)(mj+1):i=acj=bda[i][j]=((n+1)(m+1)j=1mD[i][j])((m+1)j=1mD[i][j]i)((n+1)j=1mD[i][j]j)+(j=1mD[i][j]ij)

所以我们需要四个树状数组来实现这个效果

举个例子(洛谷P4514)//本题使用CDQ分治会更好

二维数组解决此类问题的缺点是内存占用大

image-20221229212819618

image-20221229212843539

具体的思路都在上面了,直接给代码咯

小小唠一下,这是本菜鸡的第一道紫题....

开心

一至三维偏序问题(逆序对+离散)

还不会,以后再说。。。

P1774, P4479,

区间最值

离线处理

例题

洛谷P3374

image-20221230162855889

image-20221230162915674

//板子题,区间修改退化为单点修改

洛谷P3368

image-20221230180407407

image-20221230180428372

//模板题练练手得了