树状数组的理解
2012-04-11 10:58
218 查看
菜鸟一个,说的不好还望指点
去年学的树状数组,现在都忘没了,复习一下。
给你一组数据,例如a[1],a[2],a[3],...a[k],..a
,求任意一段区间和(比如说从a[i]---a[j]的所有数的和),线性求解,时间复杂度O(n),规模较大时,会TLE。怎么办?思路:以空间换时间。提前存储一些数据的和。怎么存储会节省时间?
方法一:构造一个和数组sum,每次输入第i个数据,就计算一下前i个数的和sum[i]。当求取前i个数和时,直接输出sum[i],O(1)时间复杂度,不很好吗。可是。。。如果我想更新一下第j个数据,那么就得连带着更新sum[j],sum[j+1],...sum
,时间复杂度又是O(n),悲剧。。。
方法其他:还有其他方法,大家自己想吧,也可以查查。
现在隆重推出重量级方法:树状数组。让求和 和更新都是lg(n)。它的奥妙在哪呢?
设这个数组为tree
(没错,就一个数组)
奥妙一:tree中存着神马东西?
举个栗子:tree[12]
解:12------>1100(把12转换为二进制)
把1100变身------>1001 1010 1011 1100
tree[1100]=a[1001]+a[1010]+a[1011]+a[1100]
也就是tree[12]=a[9]+a[10]+a[11]+a[12]
也就是1100从右向左数第一个1,把1及1后边的0去掉(把100去掉),让a[1001]一直加到a[1100]
再举个复杂点儿的栗子:tree[40]
解:40------>101000
101000变身-------->100001 100010 ... 101000
tree[101000]=a[100001]+a[100010]+...+a[101000]
也就是tree[40]=a[31]+a[32]+...+a[40]
也就是101000从右向左数第一个1,把1及1后边的0去掉(把1000去掉),让a[100001]一直加到a[101000]
上两个图,就看清楚了
灰色的是tree[i]大家可以把1~16转换为二进制验证一下
再上个图片
c是tree数组
明白了吗?
那则么求出从右向左数第一个1和它后边的0呢?
奥妙二:怎么求出从右向左数第一个1和它后边的0?
很简单。
知道补码肿么回事吗?正数的补码,不变;负数的补码取反加1,或者说,从右向左数一直不要变,直到第一个1,把1左边的数取反,这个大家都懂吧~
好的,继续。给你一个数x(一定是正数哦),求出他从右向左数第一个1和它后边的0?
1.求出它的补码,0-x
2.x与0-x相与
设出从右向左数第一个1和它后边的0这个数为low(x)
则low(x)=x&(-x)
奥妙三:求和
有了上边的基础,求和就好理解了
例如,求sum(13) 前13个数的和
13变身-------->1101 再变身 (先什么都不去掉)
--------->1101(去掉1,lowbit(1101))
---------->1100(去掉100,lowbit(100))
---------->1000
所以 sum[1101]=tree[1101]+tree[1100]+tree[1000]
也就是sum[13]=tree[13]+tree[12]+tree[8]
为什么这么算是对的?
好,复习一下奥妙二
tree[13]=a[13]
tree[12]=a[9]+a[10]+a[11]+a[12]
tree[8]=a[8]+a[7]+...+a[1]
很神奇吧~~~~~~
上个图:
[align=center] [/align]
求sum[13]
[align=center] [/align]
[align=center] [/align]
[align=center]写个求和函数[/align]
奥妙四:更新
更新一个数a[i],就得连带着更新存着a[i]的一些tree数组。怎么更新?想想~~~
没错!每次给i加lowbit(i)即可
举个栗子:)
更新a[5],比如给a[5]加上个新值nval
0101(+lowbit(0101))------->0110(+lowbit(0110))------->1000(+lowbit(1000))-------->10000
tree[5]+=nval tree[6]+=nval tree[8]+=nval tree[16]+=nval
上个图
[align=center] [/align]
给a[5]加-1
[align=center] [/align]
[align=center]写个函数:[/align]
好了,写到这儿,望各位多多指点
部分图片来自http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binaryIndexedTrees#find
部分图片不知道原创作者。。。
转载请说明出处,谢各位了
[align=center]http://www.cnblogs.com/inpeace7/archive/2012/04/10/2441352.html [/align]
去年学的树状数组,现在都忘没了,复习一下。
给你一组数据,例如a[1],a[2],a[3],...a[k],..a
,求任意一段区间和(比如说从a[i]---a[j]的所有数的和),线性求解,时间复杂度O(n),规模较大时,会TLE。怎么办?思路:以空间换时间。提前存储一些数据的和。怎么存储会节省时间?
方法一:构造一个和数组sum,每次输入第i个数据,就计算一下前i个数的和sum[i]。当求取前i个数和时,直接输出sum[i],O(1)时间复杂度,不很好吗。可是。。。如果我想更新一下第j个数据,那么就得连带着更新sum[j],sum[j+1],...sum
,时间复杂度又是O(n),悲剧。。。
方法其他:还有其他方法,大家自己想吧,也可以查查。
现在隆重推出重量级方法:树状数组。让求和 和更新都是lg(n)。它的奥妙在哪呢?
设这个数组为tree
(没错,就一个数组)
奥妙一:tree中存着神马东西?
举个栗子:tree[12]
解:12------>1100(把12转换为二进制)
把1100变身------>1001 1010 1011 1100
tree[1100]=a[1001]+a[1010]+a[1011]+a[1100]
也就是tree[12]=a[9]+a[10]+a[11]+a[12]
也就是1100从右向左数第一个1,把1及1后边的0去掉(把100去掉),让a[1001]一直加到a[1100]
再举个复杂点儿的栗子:tree[40]
解:40------>101000
101000变身-------->100001 100010 ... 101000
tree[101000]=a[100001]+a[100010]+...+a[101000]
也就是tree[40]=a[31]+a[32]+...+a[40]
也就是101000从右向左数第一个1,把1及1后边的0去掉(把1000去掉),让a[100001]一直加到a[101000]
上两个图,就看清楚了
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |
a | 1 | 0 | 2 | 1 | 1 | 3 | 0 | 4 | 2 | 5 | 2 | 2 | 3 | 1 | 0 | 2 |
再上个图片
c是tree数组
明白了吗?
那则么求出从右向左数第一个1和它后边的0呢?
奥妙二:怎么求出从右向左数第一个1和它后边的0?
很简单。
知道补码肿么回事吗?正数的补码,不变;负数的补码取反加1,或者说,从右向左数一直不要变,直到第一个1,把1左边的数取反,这个大家都懂吧~
好的,继续。给你一个数x(一定是正数哦),求出他从右向左数第一个1和它后边的0?
1.求出它的补码,0-x
2.x与0-x相与
设出从右向左数第一个1和它后边的0这个数为low(x)
则low(x)=x&(-x)
奥妙三:求和
有了上边的基础,求和就好理解了
例如,求sum(13) 前13个数的和
13变身-------->1101 再变身 (先什么都不去掉)
--------->1101(去掉1,lowbit(1101))
---------->1100(去掉100,lowbit(100))
---------->1000
所以 sum[1101]=tree[1101]+tree[1100]+tree[1000]
也就是sum[13]=tree[13]+tree[12]+tree[8]
为什么这么算是对的?
好,复习一下奥妙二
tree[13]=a[13]
tree[12]=a[9]+a[10]+a[11]+a[12]
tree[8]=a[8]+a[7]+...+a[1]
很神奇吧~~~~~~
上个图:
[align=center] [/align]
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |
a | 1 | 0 | 2 | 1 | 1 | 3 | 0 | 4 | 2 | 5 | 2 | 2 | 3 | 1 | 0 | 2 |
[align=center] [/align]
[align=center] [/align]
[align=center]写个求和函数[/align]
#define lowbit(x) ((x)&(-x)) int getsum(int i) { int sum=0; while(i>0) { sum+=tree[i]; i-=lowbit(i); } return sum; }
奥妙四:更新
更新一个数a[i],就得连带着更新存着a[i]的一些tree数组。怎么更新?想想~~~
没错!每次给i加lowbit(i)即可
举个栗子:)
更新a[5],比如给a[5]加上个新值nval
0101(+lowbit(0101))------->0110(+lowbit(0110))------->1000(+lowbit(1000))-------->10000
tree[5]+=nval tree[6]+=nval tree[8]+=nval tree[16]+=nval
上个图
[align=center] [/align]
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |
a | 1 | 0 | 2 | 1 | 1 | 3 | 0 | 4 | 2 | 5 | 2 | 2 | 3 | 1 | 0 | 2 |
[align=center] [/align]
[align=center]写个函数:[/align]
#define lowbit(x) ((x)&(-x)) void update(int i,int val) { while(i<=n) { tree[i]+=val; i+=lowbit(i); } }
好了,写到这儿,望各位多多指点
部分图片来自http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=binaryIndexedTrees#find
部分图片不知道原创作者。。。
转载请说明出处,谢各位了
[align=center]http://www.cnblogs.com/inpeace7/archive/2012/04/10/2441352.html [/align]
相关文章推荐
- POJ2309BST【树状数组的理解】
- hdu5975_Aninteresting game_树状数组理解
- 深入理解树状数组
- 仔细理解线段树和树状数组
- 树状数组浅显理解以及Ultra-QuickSort的AC代码
- 树状数组理解
- 树状数组点更新,区间更新理解
- POJ2309BST【树状数组的理解】
- 树状数组的理解
- 树状数组的大概理解
- -----树状数组的理解
- 对于树状数组的理解
- HDU1166 敌兵布阵 树状数组|线段树-构造完全二叉树(理解)
- POJ 3321 Apple Tree (树状数组)
- hdu2836之树状数组
- POJ 2299 Ultra-QuickSort(树状数组+离散化)
- HDU-4217(树状数组)
- hdoj 3450 Counting Sequences 【离散化 + 树状数组优化dp】
- 树状数组
- hdu5338 贪心+线段树+二分+树状数组