这几天一直学习树状数组,可是还是一知半解。
如果给定一个数组,要你求里面所有数的和,一般都会想到累加,但是当那个数组很大的时候,累加就显得太耗时了,时间复杂度为O(n),并且采用累加的方法还有一个局限,那就是,当改掉数组中的某一个元素之后,仍然让你求数组中某段元素的和,就显得麻烦 了,所以我们就要用到树状数组,它到 时间复杂度为O(lgn),相比之下就快的多,。下面就说一下什么是树状数组:
树状数组主要用到一个累加的思想,下面来看一下树状数组图,方便理解:
根据上图可知:
c1=a1
c2=a1+a2
c3=a3
c4=a1+a2+a3+a4
c5=a5
c6=a5+a6
c7=a7
c8=a1+a2+a3+a4+a5+a6+a7+a8
c9=a9
等等
分析上面的几组式子可知,当i为奇数时,ci=ai;
当i为偶数时,就要看i的因子中最多有二的多少次幂,例如,6的因子中有二的一次幂,等于2:
所以c6=a5+a6(由六向前数俩个数的和),4的因子中有二的俩次幂,,等于四,所以c4=a1+a2+a3+a4(由四向前数四个数的和);
(1)有公式cn=a(n-a^k+1)+.....an(其中k为n的二进制表示中从右往左数的0的个数)
下面是求a^k方法:
int low(int n)
{
return n&(-n);
}
low()的返回值就是2^k次方的值。
求出2^k之后,数组c的值就都可以求出来了。接下来我们要求数组中所有元素的和。
(2)求数组的和的算法如下:
(a)首先,令sum=0,执行下一步;
(b)判断,如果n>0的话,就令sun=sum+cn,继续执行下一步,否则,终止算法,返回sum的值。
(c)n=n-low(n)(将n的二进制表示的最后一个零删掉),返回第二步继续执行。
代码实现:
int sum(int n)
{
int sum=0;
while(n>0)
{
sum=sum+c[n];
n=n-low(n);
}
return sum;
}
(3)当数组中的元素有改变时,树状数组就可以发挥它的优势了,算法如下:(数组改变时,是给某一个结点i加上x)
(a)当i<=n时,执行下一步,否则的话,算法接受;
(b)c[i]=c[i]+x;i=i+low(i)(在i的二进制表示的最后加零),返回第一步。
代码实现:
<pre name="code" class="csharp">void change(int i,int x)
{
while(i<=n)
{
c[i]=c[i]+x;
i=i+low(i);
}
}
这是一道模板题:请直接分析代码即可,注意这种题目cin和cout容易TLE
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
using namespace std;
int N;
int a[50010];
int c[50010];
int lowbit(int x)
{
return x&-x;
}
void update(int i , int num)
{
while(i <= N)
{
c[i] += num;
i += lowbit(i);
}
}
int getsum(int x)
{
int sum = 0;
while(x > 0)
{
sum += c[x];
x -= lowbit(x);
}
return sum;
}
int main()
{
int t,z;
scanf("%d",&t);
z = 1;
while(t--)
{
scanf("%d",&N);
memset(c,0,sizeof(c));
for(int i = 1; i <= N; i++)
{
int m;
scanf("%d",&m);
update(i,m);
}
printf("Case %d:\n",z++);
char str[10];
while(scanf("%s",str) && str[0] != 'E')
{
if(str[0] == 'Q')
{
int i,j;
scanf("%d%d",&i,&j);
printf("%d\n",getsum(j) - getsum(i-1));
}
else if(str[0] == 'A')
{
int x,y;
scanf("%d%d",&x,&y);
update(x,y);
}
else if(str[0] == 'S')
{
int p,q;
scanf("%d%d",&p,&q);
update(p,-q);
}
}
}
return 0;
}