树状数组

树状数组(Fenwick tree,又名binary indexed tree),是一种很实用的数据结构。它通过用节点i,记录数组下标在[ i –2^k + 1, i]这段区间的所有数的信息(其中,k为i的二进制表示中末尾0的个数,设lowbit(i) = 2^k),实现在O(lg n) 时间内对数组数据的查找和更新。

树状数组的传统解释图,不能很直观的看出其所能进行的更新和查询操作。其最主要的操作函数lowbit(k)与数的二进制表示相关,本质上仍是一种二分。因而可以通过二叉树,对其进行分析。事实上,从二叉树图,我们对它所能进行的操作和不能进行的操作一目了然。和前面提到的点树类似,先画一棵二叉树,然后对节点中序遍历(点树是采用广度优先),每个节点仍然只记录左子树信息,见图:

 

由于采用的是中序遍历,从节点1到节点k时,刚好有k个叶子被统计。

可以证明:

     叶子k,一定在节点k的左子树下。

     以节点k为根的树,其左子树共有叶子lowbit(k)

节点k的父节点是:k + lowbit(k) 或 k - lowbit(k)

节点k + lowbit(k) 是节点k的最近父节点,且节点k在它的左子树下。

节点k - lowbit(k) 是节点k的最近父节点,且节点k在它的右子树下。

节点k,统计的叶子范围为:(k - lowbit(k),  k]。

节点k的左孩子是:k - lowbit(k) / 2

 

下面分析树状数组两面主要应用:

1 更新数据x,进行区间查询。

2 更新区间,查询某个数。

由于,树状数组只统计了左子树的信息,因而只能查询更新区间[1, x]。只在在满足[x,y]的信息可以由[1,x-1]和[1,y]的信息推导出时,才能进行区间[x,y]的查询更新。这也是树状数组不能用于任意区间求最值的根本原因。

 

先定义两个集合:

up_right(k) : 节点k所有的父节点,且节点k在它们的左子树下。

up_left(k) :  节点k所有的父节点,且节点k在它们的右子树下。

 

1  更新数据x,查询区间[1,y]。

显然,更新叶子x,要找出叶子x在哪些节点的左子树下。因而节点k、所有的up_right(k)

都要更新。

查询[1, y],实际上就是把该区间拆分成一系列小区间,并找出统计这些区间的节点。可以通过找出y在哪些节点的右子树下,这些节点恰好不重复的统计了区间[1, y-1]。因而要访问节点y、所有的up_left(y)。

 

2 更新区间[1,y],查询数据x

  这和前面的操作恰好相反。与前面的最大不同之处在于:节点保存的不再是其叶子总个数这些信息,而是该区间的所有叶子都改变了多少。也就是说:每个叶子的信息,分散到了所有对它统计的节点上。因此操作和前面相似:

  更新[1,y]时,更新节点y、所有up_left(y)。

  查询x时,  访问x、所有up_right(x)。

 

区间问题 :

(1)“改点求段”型,即对于序列A有以下操作:

【1】修改操作:将A[x]的值加上c;

【2】求和操作:求此时A[l..r]的和。

这是最容易的模型,不需要任何辅助数组。树状数组中从x开始不断减lowbit(x)(即x&(-x))可以得到整个[1..x]的和,而从x开始不断加lowbit(x)则可以得到x的所有前趋。

一维数组:

 1 传入数组A[]的长度n,及对A[i]出的修改,传出A[1`````i]处的和
2 (1): add(i,w) 在数组A[i]加上w
3 (2): sum(i) 求A[1````i]的和
4
5 #define lowbit(x) ((x)&(-(x)))
6 const int Max = 50005;
7
8 int n, ar[Max];
9
10 void add(int i, int w){
11 while(i <= n){
12 ar[i] += w;
13 i += lowbit(i);
14 }
15 }
16
17 int sum(int i){
18 int ans = 0;
19 while(i > 0){
20 ans += ar[i];
21 i -= lowbit(i);
22 }
23 return ans;
24 }
25 void init(){
26 memset(ar, 0, sizeof(ar));
27 }



二维数组:

 1 传入矩阵行列rol,col;
2 操作1:add(i,j,w)在A[1```i][1````j]加上w
3 操作2:sum(i,j)求A[i][j]处的值
4
5 const int Max = 1005;
6
7 int row, col;
8 int ar[Max][Max];
9
10 int lowbit(int x){
11 return x & (-x);
12 }
13 void add(int i, int j, int w){
14 int tmpj;
15 while(i <= row){
16 tmpj = j;
17 while(tmpj <= col){
18 ar[i][tmpj] += w;
19 tmpj += lowbit(tmpj);
20 }
21 i += lowbit(i);
22 }
23 }
24 int sum(int i, int j){
25 int tmpj, ans = 0;
26 while(i > 0){
27 tmpj = j;
28 while(tmpj > 0){
29 ans += ar[i][tmpj];
30 tmpj -= lowbit(tmpj);
31 }
32 i -= lowbit(i);
33 }
34 return ans;
35 }
36 void init(){
37 memset(ar, 0, sizeof(ar));
38 }

 

(2)“改段求点”型,即对于序列A有以下操作:

【1】修改操作:将A[l..r]之间的全部元素值加上c;

【2】求和操作:求此时A[x]的值。

这个模型中需要设置一个辅助数组B:B[i]表示A[1..i]到目前为止共被整体加了多少(或者可以说成,到目前为止的所有ADD(i, c)操作中c的总和)。

则可以发现,对于之前的所有ADD(x, c)操作,当且仅当x>=i时,该操作会对A[i]的值造成影响(将A[i]加上c),又由于初始A[i]=0,所以有A[i] = B[i..N]之和!而ADD(i, c)(将A[1..i]整体加上c),将B[i]加上c即可——只要对B数组进行操作就行了。

这样就把该模型转化成了“改点求段”型,只是有一点不同的是,SUM(x)不是求B[1..x]的和而是求B[x..N]的和,此时只需把ADD和SUM中的增减次序对调即可(模型1中是ADD加SUM减,这里是ADD减SUM加)。

 1 操作1: add(a,b,w) 在A[a```b]上加上w
2 操作2: sum(a) 查询A[a]处的值
3
4 #define lowbit(x) ((x)&(-(x)))
5 const int Max = 50005;
6
7 int n, ar[Max];
8
9 void add(int i, int w){
10 while(i <= n){
11 ar[i] += w;
12 i += lowbit(i);
13 }
14 }
15
16 int sum(int i){
17 int ans = 0;
18 while(i > 0){
19 ans += ar[i];
20 i -= lowbit(i);
21 }
22 return ans;
23 }
24 void init(){
25 memset(ar, 0, sizeof(ar));
26 }
27 void addq(int a,int b,int w){
28 if(a-1>0) add(a-1,-w);
29 add(b,w);
30 }
31 int sumq(int a){
32 return sum(n)-sum(a-1);
33 }

 

(3)“改段求段”型,即对于序列A有以下操作:

【1】修改操作:将A[l..r]之间的全部元素值加上c;

【2】求和操作:求此时A[l..r]的和。

这是最复杂的模型,需要两个辅助数组:B[i]表示A[1..i]到目前为止共被整体加了多少(和模型2中的一样),C[i]表示A[1..i]到目前为止共被整体加了多少的总和(或者说,C[i]=B[i]*i)。

对于ADD(x, c),只要将B[x]加上c,同时C[x]加上c*x即可(根据C[x]和B[x]间的关系可得);

而ADD(x, c)操作是这样影响A[1..i]的和的:若x<i,则会将A[1..i]的和加上x*c,否则(x>=i)会将A[1..i]的和加上i*c。也就是,A[1..i]之和 = B[i..N]之和 * i + C[1..i-1]之和。
这样对于B和C两个数组而言就变成了“改点求段”(不过B是求后缀和而C是求前缀和)。
另外,该模型中需要特别注意越界问题,即x=0时不能执行SUM_B操作和ADD_C操作!

 1 操作1: add(a,b,w) 在A[a```b]上加上w
2 操作2: sum(a,b) 查询A[a```b]处的和
3
4 #define lowbit(x) ((x)&(-(x)))
5 const int Max = 50005;
6
7 int n, ar[Max],br[Max];
8
9 void addar(int i, int w){
10 while(i <= n){
11 ar[i] += w;
12 i += lowbit(i);
13 }
14 }
15 void addbr(int i, int w){
16 while(i <= n){
17 br[i] += w;
18 i += lowbit(i);
19 }
20 }
21
22 int sumar(int i){
23 int ans = 0;
24 while(i > 0){
25 ans += ar[i];
26 i -= lowbit(i);
27 }
28 return ans;
29 }
30 int sumbr(int i){
31 int ans = 0;
32 while(i > 0){
33 ans += br[i];
34 i -= lowbit(i);
35 }
36 return ans;
37 }
38
39 void addq(int a,int b,int w){
40 if(a-1>0) addar(a-1,-w),addbr(a-1,-(a-1)*w);
41 addar(b,w); addbr(b,b*w);
42 }
43 int sumq(int a,int b){
44 a--;
45 int ans=(sumar(n)-sumar(b-1))*b+sumbr(b-1);
46 ans-=(sumar(n)-sumar(a-1))*a+sumbr(a-1);
47 return ans;
48 }
49
50 void init(){
51 memset(ar, 0, sizeof(ar));
52 memset(br, 0, sizeof(br));
53 }



参考文献 :

 http://www.cnblogs.com/flyinghearts/archive/2011/04/11/2013111.html

http://www.cppblog.com/MatoNo1/archive/2011/03/19/142226.html

posted @ 2012-02-29 21:06  HaoHua_Lee  阅读(215)  评论(0编辑  收藏  举报