参考:树状数组
简述:一个用来求数组列前缀和的数据结构,可以在O( log(n) )的时间内得到数列的任意前缀和,同样以相同时间对某项加一个常数,是一个很强大的数据结构。
认识:有段时间一直以为树状数组就是普通的数组,只不过可以用它来存储二叉查找树,后来才知道这货其实是一个很难的东西。不过说难,其实它也是一个蛮简单、蛮好入手的一个东西——关键代码相当简洁,只是有点难以理解,但是,用一个图就可以表达清楚了:
PS:这里盗图一用(来源)
如果还没有清楚,很正常,来看看一个具体的例子:
有数组a,a = {1, 2, 3, 4, 5, 6, 7, 8},然后有一个bit数组,储存树状数组用的,它的值如下:
bit[1] = a[1] ;
bit[2] = a[1]+a[2] ;
bit[3] = a[3];
bit[4] = a[1]+a[2]+a[3]+a[4] ;
bit[5] = a[5] ;
bit[6] = a[5]+a[6];
bit[7] = a[7] ;
bit[8] = a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8] .
然后可以发现,上面这个数组的各个值,又有一个规律:
bit[1] = a[1] ;
bit[2] = a[2]+bit[1] ;
bit[3] = a[3];
bit[4] = a[4]+bit[2]+bit[3] ;
bit[5] = a[5] ;
bit[6] = a[6]+bit[5] ;
bit[7] = a[7] ;
bit[8] = a[8]+bit[4]+bit[6]+bit[7] .
把上面的八个式子跟那个图对应着看,有没有发现什么神奇的东西?
嗯,其实那个图,想表达的就是那几个式子,从那几个例子我们其实可以隐隐约约看出bit数组的规律,但是这东西怎么用程序来表达呢?
1、首先得有一个预备函数lowbit( int ),返回参数转为二进制后最后一个1的位置代表的数值:
int lowbit(int num){
return num&(-num);
}
这里用了一些位运算操作。
2、然后就是bit数组的构建了,等于啥呢?
void build()
{
for(int i=1;i<=n;i++)
{
bit[i]=a[i];
for(int j=i-1;j>i-lowbit(i);j-=lowbit(j))
bit[i]+=bit[j];
}
}
这段代码表达的就是那个图,至于为啥,读者自己思考吧,会有很大收获哦!
3、接下来看看这东西怎么做到高速求前缀和的:
int sum(int index)
{
int res=0;
for(int i=index;i>0;i-=lowbit(i))
res+=bit[i];
return res;
}
如果你理解了上面的build函数,那这段代码必然是秒懂,这里我就举个例子吧:
还是上面那个8位数组,用sum数组表示a数组的前缀和:
sum[1] = bit[1] ;
sum[2] = bit[2] ;
sum[3] = bit[2]+bit[3] ;
sum[4] = bit[4] ;
sum[5] = bit[4]+bit[5] ;
sum[6] = bit[4]+bit[6] ;
sum[7] = bit[4]+bit[6]+bit[7] ;
sum[8] = bit[8] .
参照代码想一下下……
4、修改怎么破,如果给a[i]增加Δ:
直接上代码吧:
void add(int index,int delta){
for(int i=index;i<=n;i+=lowbit(i))
bit[i]+=delta;
}
看懂了2,就看懂了3、4!
总结:
1、优美的数据结构,不能再美!
2、用二进制思考问题!