————————————————————————这些是转的,出处不明———————————————————————————————

   树状数组比较适合单个元素改变,反复求部分和,或者区间更新,单点求值。

   先看的是一维的树状数组。
  树状数组是一个很天才的想法,考虑这样的一种情景,对于一组数据,你经常要求他们某个区间的和,而却这组数据里的元素会经常的改变,最朴素的想法就是暴力,O(1)的修改,O(n)的查询,或O(n)的修改O(1)的查询(就是记录)。第二种想法就是线段树,查询和修改的复杂度都是O(logn),线段树的编程复杂度比较高,常数因子也较大。有一种时间复杂度也是O(logn)的而且编程复杂度很简单的方法,就是用树状数组。树状数组的灵感是来源于二进制、线段树和O(1)查询O(n)修改算法(其实是我自己的灵感啦哈哈),二进制有01组成,每一个数字都有自己对应的一个二进制,既然线段数是把数据按二分的思想,把区间分成两个一样大小的区间,把大问题分解成两个小的子问题,那么在一组规模大小是10010110的数据,同样的我们也可以把区间分成一个个子区间,把大问题分解成一个个小问题,那么要怎么分解呢?看这二进制就明白了,我们要把区间分解成一个个大小不一的子区间,使它们加起来刚好就是原来的区间,很明显这个二进制可以分成:

高级数据结构之树状数组

高级数据结构之树状数组

高级数据结构之树状数组

如果我们要求1到10010100的和,即可用四个子区间组成原问题
[1,10]
[11,100]
[1001,10000]
[10001,10010100]
把它们加起来就是了,这就是说我们每次都把10010110最右边的“1”拿出来,作为子区间的右边界,左边界就是前一个子区间的右边界加1,第一个子区间的左边界是1.用位运算,把最右边的1分解,i&(i^(i-1))即i&(-i),这是自低向上的把区间分解出来,接下来就是定义tree[i]来递推了,按照上面的分法,很难定义,所以我们自顶向下的分区间试试看能不能容易的定义,分的四个区间:
[10010101,10010110]
[10010001,10010100]   
[10000001,10010000]
[00000001,10000000]
这样定义起来就很方便了,我們可以看到右區間就是每往下都去掉一個最右邊的1,tree[i]就是区间[i-(i&-i)+1,i]的和,由这里的定义知道,树状数组要从1开始计数,而不是c/c++一向的0开始。求和函数就这样写:

1 int get_sum(int i)
2 {
3     int sum = 0;
4     while(i) {
5             sum += tree[i];
6             i -= i & -i;
7     }
8     return sum;
9 } 
View Code

相关文章: