第19天

来源:互联网 发布:提醒功能的软件 编辑:程序博客网 时间:2024/05/01 21:14

树状数组这部分,基本会用到3个数组---原始数组a[]、树状数组c[]、求和数组sum[],主要有两个操作---更新(单点,某段区间)与查询(单点,区间求和)。充分运用了数据的二进制形式,达到高效(lowbit(i)=2^k)。基于以上,树状数组扩展了强大的功能。

许多功能其实就是对更新和查询的整合,运用很灵活,虽然理解,但自己很难直接想到,在下面列出:

1.三种求和模式:第一种是单点更新、区间查询,就是调用基本操作。第二种是区间更新、单点查询,此处没有对每个区间元素更新,而是通过灵活操作降低了复杂度。调用两次更新,首先对左端点L更新(value),然后对右端点下个位置R+1更新(-value),这样只是完成了这两个点及父亲结点的更新。在具体查询某点时,调用了求和查询的操作,此时sum[i]的值才是真正更新后的元素的值,也是通过sum数组完成对整个区间更新后的数据的保存。第三种是区间更新、区间查询,构建2个树状数组m[]、n[],对m进行单点更新、区间查询,对n进行区间更新、单点查询,这种模式的步骤要繁杂些。

2.树状数组的基本功能----求比某点x小的点的个数。具体就是先遍历数组,每个位置求和,然后再进行更新。数据范围很大时,要进行离散化,就是先进行排序再重新编号,在一些情境下还可以在离散化时倒序编号。

3.树状数组因独特的结构而形成了二分条件。并且今天看题目时,接触了自己没有用过的一种二分思路。题目大意为.sum[k]=y,已知y,要求返回下标k。对于l,r,mid,我在通过比较修改上下限后,进行操作 mid=(l+r)/2。今天看到的这种,虽然也是比较后修改上下限,但在二分时仅对上限操作,即r/=2;

4.对于lowbit()的充分运用。其实就是对下标间对应关系的充分运用,体现了数据二进制形式的优越性。如,要求a[i],我们可以通过前缀和sum[i]-sum[i-1],但简化来看,可以通过树状数组c[]来实现,因为sum数组元素包含共同树状数组元素,本身便可抵消。关键问题在于共同元素从哪个下标开始,这里便充分运用了数据的二进制形式。

以下摘自http://www.hawstein.com/posts/binary-indexed-trees.html#readf

读取某个位置的实际频率

上面我们已经讨论了如何读取指定索引的累积频率值(即c[idx]),很明显我们无法通过 tree[idx]直接读取某个位置的实际频率f[idx]。有人说,我们另外再开一个数组来存储f数 组不就可以了。这样一来,读和存f[idx]都只需要O(1)的时间,而空间复杂度则是O(n)的。 不过如果考虑到节约内存空间是更重要的话,我们就不能这么做了。接下来我们将展示在不 增加内存空间的情况下,如何读取f[idx]。(事实上,本文所讨论的问题都是基于我们只维 护一个tree数组的前提)

事实上,有了前面的讨论,要得到f[idx]是一件非常容易的事: f[idx] = read[idx] - read[idx-1]。即前idx个数的和减去前idx-1个数的和, 然后就是f[idx]了。这种方法的时间复杂度是2*O(log n)。下面我们将重新写一个函数, 来得到一个稍快一点的版本,但其本质思想其实和read[idx]-read[idx-1]是一样的。

假如我们要求f[12],很明显它等于c[12]-c[11]。根据上文讨论的规律,有如下的等式: (为了方便理解,数字写成二进制的表示)

c[12]=c[1100]=tree[1100]+tree[1000]c[11]=c[1011]=tree[1011]+tree[1010]+tree[1000]f[12]=c[12]-c[11]=tree[1100]-tree[1011]-tree[1010]

从上面3个式子,你发现了什么?没有错,c[12]和c[11]中包含公共部分,而这个公共部分 在实际计算中是可以不计算进来的。那么,以上现象是否具有一般规律性呢?或者说, 我怎么知道,c[idx]和c[idx-1]的公共部分是什么,我应该各自取它们的哪些tree元素来做 差呢?下面将进入一般性的讨论。

让我们来考察相邻的两个索引值idx和idx-1。我们记idx-1的二进制表示为a0b(b全为1), 那么idx即a0b+1=a1b^- .(b^- 全为0)。使用上文中读取累积频率的算法(即read函数) 来计算c[idx],当sum加上tree[idx]后(sum初始为0),idx减去最后的1得a0b^- , 我们将它记为z。

用同样的方法去计算c[idx-1],因为idx-1的二进制表示是a0b(b全为1),那么经过一定数量 的循环后,其值一定会变为a0b^- ,(不断减去最后的1),而这个值正是上面标记的z。那么, 到这里已经很明显了,z往后的tree值是c[idx]和c[idx-1]都共有的, 相减只是将它们相互抵消,所以没有必要往下再计算了。

也就是说,c[idx]-c[idx-1]等价于取出tree[idx],然后当idx-1不等于z时,不断地减去 其对应的tree值,然后更新这个索引(减去最后的1)。当其等于z时停止循环(从上面的分析 可知,经过一定的循环后,其值必然会等于z)。