关于“树状数组”

来源:互联网 发布:商之翼yii2源码 编辑:程序博客网 时间:2024/06/05 01:10

线段树有的时候是比较复杂没有必要的,于是就会造成很大的时间复杂度和空间复杂度。于是,树状数组是线段树在某些情况下很大的优化

首先先代入一张数状数组的图。


(注:此图乃是转载之)


图中将c数组标记出来。


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


到了c8停止我们可以看到8分为可以分为1-4,5-8两个部分,而这两个部分又可以分为1-23-45-67-8,在其他情况下也亦是这样。


那么很容易可以看出,树状数组是利用二分的思想来的。

那么接下来就是很巧妙的地方。


有一个东西two,那么twok)表示的意思是将k的后缀到第一个1位置的数(二进制情况下),也就是说在k的二进制情况下,保留后面的0和最后一个1.


具体怎么做呢?


Twok=k&-k


举一个例子:

E.g


k10

1010=10102

-1010=01102 //显而易见,一个正数的二进制情况下的负数形式是正数的二进制取反加一。


那么

 1010

&0110

----------

 0010

也就是(102


对于一个树状数组cc[k]表示从a[k]开始往左twok)个数的和,这个在图中很容易可以看出来。

那么怎么对于一个树状数组进行更改呢?


这也用到了two这个东西,是的,特别巧妙。

再在图中找一个样例吧。

就比如我们当前要把a3更改为5

那么我们就必须更改c3c4c8,这一点在图中也可以看出来。


那么(310=00112

       (410=01002

       (810=10002


可以在二进制中发现,在二进制中,每一次都往高位改1

那么就可以借助two继续修改,也就是设当前这个位置在k,那么下一个位置就是在k+twok。将例子带进来看一下。

 (00112+00112&-00112

=00112+00112&11012

=00112+00012

=01002

=410


下一个就不举例了,是不是很神奇?

那为什么会这么神奇呢?主要是因为树状数组的二分的分组性质吧,这么奇妙的东西我只能抽象化的理解,就比如一个c,他的父亲肯定包含他,他的爷爷肯定也包含他,以此类推。


那么再说说求和的问题。


前面说过,c[k]表示从a[k]开始往左twok)个数的和


比如求a2a4的和

那么就可以将将14的和找出来,再将12-1的和找出来,相减,很显然是一个前缀和的做法,就不必多说了。

那么接下来就看一下树状数组是怎么实现的。

修改:

void xiugai(int v,int p){    while (p<=n)    {       c[p]=c[p]+v;       p=p+two(p);    }}


求和

int qiuhe(int z){    int sum1=0;    while (z!=0)    {       sum1=sum1+c[z];       z=z-two(z);    }    return sum1;}


关于two很简单

int two(int x){    return (x&(-x));}


这就是最基本的树状数组,应根据不同的题中不同情况来决定如何使用。


难点:

1,如何理解树状数组的思想(√)

2two的具体作用(×)


最后,将树状数组的思想稍微抽象化一点,从二进制来考虑树状数组的奇妙应该不难了(即使我现在还是没有完全弄懂)


题目:

1,求和(√)

2,星星点灯(√)

3,指纹(×)

原创粉丝点击