树状数组——————二分索引树
来源:互联网 发布: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
中央情报局要研究敌人究竟演习什么战术,所以Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek每次询问的段都不一样,所以Tidy不得不每次都一个一个营地的去数,很快就精疲力尽了,Derek对Tidy的计算速度越来越不满:"你个死肥仔,算得这么慢,我炒你鱿鱼!”Tidy想:“你自己来算算看,这可真是一项累人的工作!我恨不得你炒我鱿鱼呢!”无奈之下,Tidy只好打电话向计算机专家Windbreaker求救,Windbreaker说:“死肥仔,叫你平时做多点acm题和看多点算法书,现在尝到苦果了吧!”Tidy说:"我知错了。。。"但Windbreaker已经挂掉电话了。Tidy很苦恼,这么算他真的会崩溃的,聪明的读者,你能写个程序帮他完成这项工作吗?不过如果你的程序效率不够高的话,Tidy还是会受到Derek的责骂的.
每组数据第一行一个正整数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条命令
对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。
1101 2 3 4 5 6 7 8 9 10Query 1 3Add 3 6Query 2 7Sub 10 2Add 6 3Query 3 10End
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>
- 二叉索引树——树状数组
- 树状数组——————二分索引树
- ZOJ3635——Cinema in Akiba(树状数组+二分)
- [整体二分+树状数组]BZOJ 2527——[Poi2011]Meteors
- 树状数组—介绍
- 树状数组—改段求段
- 树状数组—改段求点
- 数据结构—树状数组
- POJ3321——树状数组_POJ树状数组初探
- 感想——树状数组和二维树状数组
- 树状数组——【模板】树状数组1
- hdu4267——树状数组
- 数据结构——树状数组
- 树状数组——HDU2352
- 树状数组——A
- 算法学习—树状数组
- ZOJ 3635——Cinema in Akiba(树状数组+二分)
- POJ 2182——Lost Cows(树状数组+二分查找 nlgnlgn)
- 使用Jlink对FL2440开发板进行烧录
- 隐马尔科夫模型
- 利用注解加拦截器实现struts2的权限设置
- iOS设计模式 - (3)简单工厂模式
- 贪心算法的分数背包问题的介绍与理解1(含c源代码)
- 树状数组——————二分索引树
- hdu 1028 Ignatius and the Princess III
- 高效求解平方根的倒数的函数实现
- 如何用Cocos2d-x创建lua项目以及lua项目如何调用cpp文件(图文讲解)
- Android Fragment IllegalStateException: Fragment not attached to Activity
- SpringMVC表单标签简介
- Three.js - 用100行javascript代码创建一座城市
- POJ 1979 Red and Black
- SVN中tag branch trunk用法详解