初学树状数组。。
这是线段树的图,由图可知,c[1]=a[1];c2=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];
可以得到这个规律,i为奇数时,c[i]=a[i],这里1,3,5,7便是例子。偶数时这里就跟2的幂有关系了,i的因子中最多有2的多少次幂,例如4,2的2次幂,为4,所以从a[4]数共4个数的值,即a[1],a[2],a[3],a[4],当i=6时,因子数最大为2,所以数2个数,即a[5],a[6],同理i=8时,2的三次幂,等于8,即a[1]+.....a[8]的值.网上查询可知,公式为
c[n]=a(n-a^k+1)+......+a[n]; (其中k为从右往走数0的个数,也即第一个出现1的位置,起点从0开始)
这里可以用一个函数计算得
int lowbit(int x)
{
return x&(-x);
}
这个函数怎样理解呢?
x=1时,x的二进制表示为(假设位数为8) 0000 0001
-x的二进制表示为 1111 1111
---> 0000 0001 =1;
x=6时 x的二进制表示为 0000 0110
-x的二进制表示为 1111 1010
---> 0000 0010 =2;
这里相信你明白了,若不懂负数的二进制表示,可百度查之
这个函数的目的就是求出2^k (k为往右数第一个出现1的位置起点从0开始或者0出现的个数,见上)
接下来就是求和了,
int Sum(int n)
{
int sum=0;
while(n>0)
{
sum+=c[n];
n=n-lowbit(n);
}
return sum;
}
这里具体该怎样理解呢?我们可以看成求一段路径的和,不同于普通的一维数组按照下标为1一次递增或递减,这里是按照树的结构,每次logn,即求路径(x,x-lowit(x),....1)为下标的和
最后修改某个结点的值
void change(int i,int x)
{
while(i<=m) //m个结点的总和
{
c[i]=c[i]+x;
i=i+lowbit(i);
}
}
如果上面求和理解了,这步应该很快理解了,改变了某个结点的值,那么对应的路径上相应的点也要改变,结束的条件即为到达根结点。
下面来看看这道题
链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=116
1,若采取常规的方法,求解,时间的复杂度为O(n^2);
2,采取树状数组的方法,AC代码:
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int c[1000008],m,Q,t;
int lowbit(int x)
{
return x&(-x);
}
int Sum(int n)
{
int sum=0;
while(n>0)
{
sum=sum+c[n];
n=n-lowbit(n);
}
return sum;
}
void change(int i,int x)
{
while(i<=m)
{
c[i]=c[i]+x;
i=i+lowbit(i);
}
}
int main()
{
scanf("%d%d",&m,&Q);
char key[10];
int start,end;
for(int i=1;i<=m;++i)
{
scanf("%d",&t);
change(i,t);
}
while(Q--)
{
scanf("%s%d%d",key,&start,&end);
if(key[0]=='Q')
printf("%d\n",Sum(end)-Sum(start-1));
else if(key[0]=='A')
change(start,end);
}
return 0;
}
参考资料来源:http://www.cnblogs.com/zhangshu/archive/2011/08/16/2141396.html
http://hi.baidu.com/czyuan_acm/item/b14bff6ab6ffd093c5d249db