在做2014年蓝桥杯的题目时,第10题说是要用到树状数组,于是就查了一下。
树状数组这个结构是在设计压缩算法时被发现的。
树状数组的基础是一个被构造出来的式子:C[i] = A[i]+A[i-1]+....+A[i-2^k+1];k代表i的二进制的最后连续0的个数,比如:对于1000和101000,k=3。
节点和子节点之间是有关系的,这种关系就是 i=j+lowbit(j);lowbit 是 j 的最低位1所代表的数字,即二进制的高位1全部清空,只留下最低位的1。比如对于 1000(8的二进制) :1000=100+lowbit(100)=110+lowbit(110)=111+lowbit(111);
其中, lowbit(j) = j&-j; 一个数加一个负号,根据补码,就是把这个数的二进制取反+1,如-10的二进制为-1010=0101+1=0110,然后用1010&0110,答案就是0010了。
(1)当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:
step1: 令sum = 0,转第二步;
step2: 假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;
step3: 令n = n – lowbit(n),转第二步。
可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:
n = n – lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。比如,求0001~0110的和就直接c[0100]+c[0110]
(2)那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。所以修改算法如下(给某个结点i加上x):
step1: 当i > n时,算法结束,否则转第二步;
step2: Ci = Ci + x, i = i + lowbit(i)转第一步。
i = i +lowbit(i)这个过程实际上也只是一个把末尾1补为0的过程。
对于数组求和来说树状数组简直太快了!
实现:
#include<iostream>
using namespace std;
int n,m,num[100001],t[200001],l,r;//n+1:数组的大小;num:原数组;t:树状数组
int lowbit(int x)
{
return x&(-x);
}
void change(int i,int p)//建立或修改;建立树状数组,t[]的第i个数加p
{
while(i<=n)
{
t[i]+=p;
i+=lowbit(i);
}
return;
}
int sum(int k)//求和;用t[]求num[1]~num[k]的和
{
int ans=0;
while(k>0)
{
ans+=t[k];
k-=lowbit(k);
}
return ans;
}
int ask(int l,int r)//求l-r区间和
{
return sum(r)-sum(l-1);
}
int main()
{
int i;
cin>>n>>m;
for(i=1;i<=n;i++) //n个数
{
cin>>num[i];
change(i,num[i]);
}
for(i=1;i<=m;i++)
{
cin>>l>>r;
cout<<ask(l,r)<<endl;
}
return 0;
}
参考:http://www.cnblogs.com/GeniusYang/p/5756975.html
http://blog.csdn.net/int64ago/article/details/7429868