ACM算法:树状数组(详细)

树状数组的用途:它用来快速修改和查询一个给定数字序列中,某个区间内值的和,这貌似是树状数组唯一

的用处。如果之前了解过线段树,你就会发现,线段树的使用范围更加广泛,而且线段树更加容易理解,但

这并不代表树状数组比线段树差。线段树虽然使用范围相对广泛,但是树状数组的性能较高,而且代码量较

少,只要理解树状数组之后,你就会发现树状数组的强大之处。


树状数组的思路:树状数组核心思想有点二分的思想(似乎很多高效的算法都和二分有关)。

话不多说,先上图:

ACM算法:树状数组(详细)_第1张图片


图中的a数组只是用来标识位置的,并不是我们实际代码中需要使用。实际代码中,我们只要使用到

c数组即可。看到图之后你应该有点感觉了吧,没错,一个明显的树状结构,而且这个树状结构所占

用的空间和装有原元素的数组空间相同,不像线段树,最差情况下,需要开比原数组大四倍的空

间。


我们这用含八个元素的数组来解释树状数组(图中的第九个元素直接忽略)。在研究算法的时候,

我们需要先研究c数组(保存1~k区间的和)是按照一种什么样的规律进行计算的。

C8 = C4 + C6 + C7 + a8,C4 = C2 + C3 + a4......。你把c数组里的每个元素按照这种方式去写出来

你就会发现,c数组中偶数(2^,即二进制中的位)下标的元素总是可以将它拆成左右两边分别进行

计算,而下标是奇数的元素的值总是等于对应原元素的值。可能你还是不明白,那我这举个例子。

例如c数组中下标为1、3、5、7、9的元素都是等于其原元素的值,而下标而可以拆成(1~1)和

(2~2)分别进行计算,下标4可以拆成(1~2)和(3~4)分别进行计算,按照这种规律,下标6应

该可以拆成(1~3)和(4~6)分别进行计算,但是事实上不是这样的,我们实际上只有2^(2的次方)

的偶数才会进行如上的二分计算,那为什么呢?理由很简单,只是为了方便实现算法和计算。


核心代码的讲解:

void add(int k, int num)
{
	while (k <= n) {
		tree[k] += num;
		k += k & (-k);
	}
}

//计算1~k区间的值和,如果你要求[q,p]区间的值,只要[1,p]-[1,q]即可
int read(int k)
{
	int sum = 0;
	while (k) {
		sum += tree[k];
		k -= k & (-k);
	}

	return sum;
}

/*
先解释一下代码中的&,&操作符只有在二进制中(1  1)时才得1,其余情况下均为0.
然后k & (-k)是什么意思,其实你只要在纸上稍微写一下你就会发现,这其实就是
取得k的二进制中的最低位的1.例如3(0011)& -3(1101) = 0001,其余的数你自
己在纸上验证,我在这就不具体讲了。
然后在add函数中,k += k & (-k)的意思就是我在思路中讲到的二分操作,也是为什
么只有2^(2的次方)才能二分操作的原因,这里不好用文字表现,我直接用二进制举例
例:k = 3,在add函数中,我们需要更改C3,C4,C8的值,所以利用k += k & (-k)语句
你就会发现,得到的k一次等于3,4,8.是不是很神奇。在read函数中也是如此,你只要在
草稿纸上稍加验算,结果一目了然。
*/

总结:树状数组不像线段树的代码过于冗长,而且效率更高。只是其使用范围较窄,需要好好斟酌使用



你可能感兴趣的:(ACM算法)