程序设计基础 树状数组

第一次尝试写甲级三十分的题目,据说要用到树状数组,去学习了一下

如下内容基本是对原作者:半根毛线code     作品的转载及注释
http://www.cnblogs.com/hsd-/p/6139376.html    
欢迎大家转去原链接


在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。

但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。

可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。

当n非常大时,程序会运行得非常缓慢。

因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。

树状数组  重点是在树状的数组

大家都知道二叉树吧

叶子结点代表A数组A[1]~A[8]

 
 .......

现在变形一下

 现在定义每一列的顶端结点C[]数组 
 如下图
 
 

C[i]代表 子树的叶子结点的权值之和// 这里以求和举例

如图可以知道

C[1]=A[1];

C[2]=A[1]+A[2];

C[3]=A[3];

C[4]=A[1]+A[2]+A[3]+A[4];

C[5]=A[5];

C[6]=A[5]+A[6];

C[7]=A[7];

C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

下面观察如下图

将C[]数组的结点序号转化为二进制

1=(001)      C[1]=A[1];

2=(010)      C[2]=A[1]+A[2];

3=(011)      C[3]=A[3];

4=(100)      C[4]=A[1]+A[2]+A[3]+A[4];

5=(101)      C[5]=A[5];

6=(110)      C[6]=A[5]+A[6];

7=(111)      C[7]=A[7];

8=(1000)    C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

对照式子可以发现  C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)例如i=8时,k=3;


那么怎么给定一个下标i,怎么找出这个k呢?注意到2的k次方,正好是下标 i的最低位的1以及其后的零组成的二进制数值

使用lowbit函数求出这个2的k次方的大小

int lowbit(int t)
{
return t&(-t);
}

注释1:计算机中负数用原数的补码,相当于原数按位取反之后加一,加的这个1使得原数按位取反后的数值,最后那几个连续的1,全部变成0(对应的原数的这几位也是0),位数最低的那个0,会被加为1,而较高的那几位不会被影响。这样以来,按位与得到的二进制数中,只有最低位的1所在的那个位不是零了。

区间查询
ok 下面利用C[i]数组,求A数组中前i项的和 
举个例子 i=7;
sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ;   前i项和
C[4]= A[1]+ A[2]+A[3]+A[4] ;    C[6]=A[5]+A[6];    C[7]=A[7];
可以推出:    sum[7]=C[4]+C[6]+C[7];
序号写为二进制: sum[(111)]= C[(111)]+C[(110)]+C[(100)];
再举个例子 i=5
sum[7]=A[1]+A[2]+A[3]+A[4]+A[5] ;   前i项和
C[4]= A[1]+ A[2]+A[3]+A[4] ;   C[5]=A[5];
可以推出:    sum[5]=C[4]+C[5];
序号写为二进制: sum[(101)]= C[(101)]+C[(100)];
 
细细观察二进制 树状数组追其根本就是二进制的应用
 注释2:对于C数组,其含有的元素和,是它的高位后的低位对应的那么多个A中元素 如C[(1110)],就包含了(1100)后的(10)个也就是两个数字A[(1101)],A[(1110)],而要求sum[(1110)]就需要C[(1110)]+C[(1100)]+C[(1000)]即1100之后的两个,1000之后的4个,0之后的8个。每个C[i]都包含了一个区间之中的A数组元素
结合代码
int getsum(int x)
{
int ans=0;
for(int i=x;i>0;i-=lowbit(i))
ans+=tree[i];
return ans;
}
单点更新
 
当我们修改A[]数组中的某一个值时  应当如何更新C[]数组呢?
注释:这要看这个数字在哪些区间里,首先肯定影响了C[i],同时,第一个包含C[i]它的区间是j=i+lowbit(i),这个加法相当于将i的lowbit前移了若干位,通过上面对区间的分析,知道这样的C[j]是包含C[i]的。
void add(intx,int y)
{
for(int i=x;i<=n;i+=lowbit(i))
tree[i]+=y;
}
文章的原作者还贴了两篇相关的练习题出来,我觉得和线段树完全联系不起来。。待我去研究一下吧TAT




  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值