树状数组——————二分索引树

来源:互联网 发布:qq三国js技能介绍 编辑:程序博客网 时间:2024/05/01 04:53

树状数组————BIT(Binary Index Tree),又称二分索引树。不得不承认,二分索引树的名字更能反应它的本质,而树状数组这个名字则更加的直观。

与其说是一种算法,不如说是一种数据结构。从名字可以直观的看出,这是一种像是树一样的数组。这就具有很多优点,能够在log(n)的时间内进行查询、求和等操作。

下面看这张图(提到树状数组必须离不开这个图)


其中以A表示原数组,C表述树状数组。

下面给出一个构造的原则(或者说树状数组的每一个结点的含义吧):C[i] = A[i-2^k+1] + .........+A[i]

其中这个k的含义是什么呢?

即表示将i转化为2进制后,从右往左数,0的个数。(大家大可不必问为什么,这个证明应该是数学问题了,找了一下没有发现相关资料于是我就作罢了...)

比如说,i = 8

8的二进制表示为00001000,从右往左数到第一个1为止共有三个0,则k = 3

那么C[i]表示的是从A[8 - 2 ^ 3 + 1] 到 A[8]也即A[1......8]的值

下面给出求一个函数LowBit(int index)用来求2^k

相信这个地方还是很难理解,我举几个例子看看。

例如,index = 6 经过LowBit运算后得出结果为2那么C[6] = A[5] + A[6]

(k == 2 刚好是表示A[i-1 + 1] + A[i - 1 + 1]这两个数的和,和前边的构造原则是一致的)

有一个简单的记忆方式,6的因子有1,2,3,6,这四个因子中有一个2是2的n次方,所以这个2就是我们想要求的那个k

在打一个比方,8的因子有1,2,4,8,其中有三个数可以表示成2的n次方(2,4,8),但那个最大的8是我们想要的k.

又如7,其因子1,7没有一个数可以表示成2的n次方,得出的结果k = 1,那么,可以得出一致结论,当index为奇数时,C[i] = A[i]

(以上红字部分可以加深对LowBit的理解)

<span style="font-family:Microsoft YaHei;font-size:14px;">int LowBit(int index){return index & (-index);}</span>

(至于为什么这么算,可以看BYvoid的链接https://www.byvoid.com/blog/binary-index-tree)

要时刻明白,LowBit这个函数,求得的是index的所有因子中满足以下条件的那个数值:

1.最大的因子

2.该因子可以表示成2的整数次幂。

巧合的是,这个LowBIt求得的值,就是当前结点的管辖范围。

什么意思呢?

大家看上图:同样以index = 8 为例。index = 8的这个叶节点的管辖范围是原数组中的1----8,这8个结点都可以看做是index结点的子结点

实际上,我们有这样的重要定理:

index += LowBit(index)得到的值,正是子节点index的父节点。

如:index = 4时 index += LowBit(index)  = 8,也就是管辖4这个结点的父节点。

相应的index -= LowBit(index)可以得到index管辖的子节点。

下面看一个例子:

敌兵布阵

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 45999    Accepted Submission(s): 19553


Problem Description
C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一清二楚,每个工兵营地的人数都有可能发生变动,可能增加或减少若干人手,但这些都逃不过C国的监视。
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
 

Input
第一行一个整数T,表示有T组数据。
每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人(1<=ai<=50)。
接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;
每组数据最多有40000条命令
 

Output
对第i组数据,首先输出“Case i:”和回车,
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
 

Sample Input
1101 2 3 4 5 6 7 8 9 10Query 1 3Add 3 6Query 2 7Sub 10 2Add 6 3Query 3 10End
 

Sample Output
Case 1:63359
 
题目关键在于要不断的修改某一点的值并且不断的求取某一段区间的和,这正是树状数组的典型应用:求一段连续区间的和。

这里,再给出两个重要的函数。

1.

____________________________________________________________________________________________________________________________________

<span style="font-family:Microsoft YaHei;font-size:14px;">void Update(int index,int value){while(index <= N){tree[index] += value;//这里不一定要加,可以给出必要的更改index += LowBit(index);}}</span>

其中N是表示原数组的容量

(这个地方其实有一容易被忽略的地方,就是C语言数组下标一般从0开始。不过LowBit(0) == 0 ,那么在这个函数中 0 += LowBit(0) 会一直得到0的结果始终<=N就会变成死循环!所以在构造原数组的时候一定要注意!)

好了,下面说明一下这个函数的功能。

因为树状数组的特殊性——某一个结点的管辖结点可能有很多个,修改子节点的时候,必须上溯到它的父节点,这一条路径的上的值都要更改,所以有了这个Update函数。

____________________________________________________________________________________________________________________________________

2.

<span style="font-family:Microsoft YaHei;font-size:14px;">int Sum(int index){int sum = 0;while(index){sum += tree[index];index -= LowBit(index);}return sum;}</span>

函数很简单,求index结点所对应的子节点和。

(其实就是从1----index的原数组的和)



看懂了图就看懂了一大半,下面给出题目代码,有兴趣的可以研究一下

HDU 1166 

<span style="font-family:Microsoft YaHei;font-size:14px;">/*--------------------------------------------------------------------12二进制:  00001100    取反:             11110011 负数补码形式哪负数补码呢我先看看负数补码何表示【负数补码其原码逐位取反符号位除外;整数加1】我返弄:先11110011-1=11110010符号位外取反:10001101 看看除符号外数:0001101 13 所数-13PS:1 & (-1) == 1*/#include <stdio.h>#include <string.h>#define maxn 50005int num[maxn];int TreeC[maxn],N;//树状数组 void Initialize(){memset(TreeC,0,sizeof(TreeC));memset(num,0,sizeof(num));}int LowBit(int index){return index & (-index);}int Sum(int index){int ans = 0;while(index > 0){ans += TreeC[index];index -= LowBit(index);}return ans;}void Update(int index,int value)//维护树状数组 {while(index <= N){TreeC[index] += value;index += LowBit(index);}}int main(){int Casenum,a,b;char str[10];scanf("%d",&Casenum);for(int cases = 1 ; cases <= Casenum ; ++cases){Initialize();scanf("%d",&N);bool over = false;for(int i = 1 ; i <= N ; ++i){scanf("%d",&num[i]);Update(i,num[i]);//读取一组数构建一次树状数组 }printf("Case %d:\n",cases);while(scanf("%s",str) != EOF){switch(str[0]){case 'Q' : { scanf("%d%d",&a,&b) ; printf("%d\n",Sum(b)-Sum(a-1)) ; } ; break;case 'A' : { scanf("%d%d",&a,&b) ; Update(a,b)  ;} ; break;case 'S' : { scanf("%d%d",&a,&b) ; Update(a,-b) ;} ; break;case 'E' : over = true;break;}if(over)break;}}return 0;}</span>


0 0