BIT(树状数组),很强大,主要体现在时间,空间,编程复杂度都要比segmen tree(线段树)小,缺点就是适用范围比segmen tree小,也就是说它能解决的问题线段树一定能够解决。
搞了我一天,才终于搞会了树状数组以及一些变形的用法。
在这里推荐一片我认为讲的比较清楚的:(http://wenku.baidu.com/link?url=91qLGMGUKpOaaaRPjjY3l_axRBpdNHvTqpKHdIiF38PdC9oCFrFIZpSV2Bw78bEogKYq4-jiXcMdw8-NX1MYeFChGLeTe3zmXOkjrfjTLzy)(下载看更流畅,不知道是不是因为我的电脑弱(。・・)ノ)
入正题。
树状数组的构造原理我这里就不详细谈了,网上很多都有。
就是一个树状数组(以下用C表示)的第i位是其原数组该位与前lowbit(i)位的和,也就是:c[i]=a[i-lowbit(i)+1]+……..+a[i](什么,你不知道lowbit是什么?恭喜恭喜)
以下为树状数组的重要用法,主要是用于求区间的和以及进行区间加减乘除之类。
基本用法:改点求段(即改动区间内一个值,求一段的和)。
口述就是在树状数组当前位置c[i]改动后,其每一个直接父亲节点都要改动,也就是c[i+lowbit(i)]也要改动,直到最后i>n时,才停止改动。
void add(int k,int x)
{
while (k<=n){//k的限制。
fre[k]+=x;//fre为树状数组。
k+=lowbit(k);
}
}
求和:求L….R的和就是求1….R的和减去1….L-1的和,问题转化为求1…..X的和。
int get(int k)
{
int sum=0;//sum为和。
while (k>0){//注意k的界限。
sum+=fre[k];
k-=lowbit(k);
}
return sum;
}
最基础的操作,记得某些题要离散化。
二:(改段求点,求段)。
树状数组一般不支持改段,但是经过改动也可以支持改段,不过求点求段就比较麻烦。
原数组a[i]不再指i这个位置的值,而是指i…n这一段区间里,每一个位置被加了多少。(这里有点难理解,注意看下面代码。)树状数组c还是在这个a上建立。
改动时,如我要将区间l..r都加1,则我们把a[l]+1,a[r+1]-1。当然,树状数组上也要改动,改动方式和上面是一样。
fo(i,1,m){
int x,y;
scanf("%d%d",&x,&y);
add(x,1);
add(y+1,-1);
}
求点:例如求第i个位置的数是多少,记为s[i],则s[i]=a[1]+a[2]+…..+a[i]。这个求和便是用树状数组直接求和就行了。
求段:比较麻烦(其实也不是很麻烦啦),因为要求改段后,a数组的定义不一样了。
由上面求点可知每一个点的求法,即s[x]=
∑xi=1a[i]
,感觉很难搞啊,我们来推一下一段区间怎么求。
假设我们现在求s[1]+s[2]+……s[x],记为S。
S=a[1]+a[1]+a[2]+…..+a[1]+a[2]+……a[x]
=a[1]* x+a[2]* (x-1)+…..+a[x]*1
=a[1]* (x+1-1)+a[2]* (x+1-2)+……+a[x]*(x+1-x)
=
∑xi=1a[i]∗(x+1−i)
=(x+1)*
∑xi=1a[i]
-
∑xi=1a[i]∗i
。
我们可以看出,只需要求得a[1]+a[2]+…..a[x]与a[1]* 1+a[2]* 2+…….+a[x]* x便是足以达到目的了,所以我们分别以a[i]与a[i]*i为基础,开两个树状数组。
然后就很好办了,我们要求l…r区间的值,即1…r与1…l-1的值,公式在上面。
这是两个树状数组的修改的代码与求和代码。
void add1(int k,int x)
{
while (k<=n){
fre[k].val+=x;//val指a[i]
k+=lowbit(k);
}
}
void add2(int k,int x)
{
while (k<=n){
fre[k].val2+=x;//val2指a[i]*i
k+=lowbit(k);
}
}
int get1(int k)
{
int sum=0;
while (k>0)
{
sum+=fre[k].val;
k-=lowbit(k);
}
return sum;
}
int get2(int k)
{
int sum=0;
while (k>0)
{
sum+=fre[k].val2;
k-=lowbit(k);
}
return sum;
}
fo(i,1,m){
int x,y;
scanf("%d%d",&x,&y);
add1(i,1);
add2(i,i); //注意,val的修改是x,则val2是x*i。
add1(y+1,-1);
add2(y+1,-i);
scanf("%d%d",&x,&y);
int sum1=(y+1)*get1(y)-get2(y),sum2=x*get1(x-1)-get2(x-1);
printf("%d\n",get1(x));//这是求点。
printf("%d\n",sum1-sum2);//zh这是求段。
}
树状数组基本是这些,注重理解,以及求段时自己推一推公式,便是可以了,实际上代码上改动并不是很大。