Mobius反演与树状数组

时间:2023-01-08 22:31:25

EMAIL:1025679612@qq.com

Blog: http://blog.csdn.net/wind_2008_06_29/article/details/

 

对于数状数组,大家应该不陌生,在此我还是说明一下它的背景吧。

我们经常会有以下要求:

对于一个序列,我们通常有两种操作,一种是取得一个子序列i到j之间的元素之和,另外一种操作是更新一个元素的值。当然,更新之后的下一次进行操作一的时候,这种改变要能够反应出来。

 

解法一:

       大家肯定都会有一种简单的想法,用一个数组Sum,其中Sum[k]表示和1个到第k个元素的和。这样,当我们需要求第m到n个元素的和的时候,我们只需要用Sum[n]-Sum[m-1]就可以了,这个算法事实上是对的,不过,也是有问题的。当更新第k个元素的时候,我们就需要同时更新Sum数组。

问题:

       以上算法事实上是对的,但是,在效率上是有问题的,这个问题反应在更新的过程,一次更新可能需要修改大量的Sum数组,这个代价是非常大的。有可能导致整个Sum数组都需要更新。

 

解法二:

       解法二就是我们通常知道的用数状数组来解决了,同样的,我们定义状态数组TA[1…n],其中TA[k]=A[k-2^i+1]+ A[k-2^k+2]+ … + A[k],其中i是K的二进制表示中末尾的0的个数。当然为了得到2^i,我们只需要以下一行代码就可以:x&(x^(x-1)),这一行代码就可以实现,其证明过程是显然的。我们不妨定义以下函数:

int lowbit(int x){

       return x&(x^(x-1));

}

我们现在的任务是要求出Sum[k],如果我们能利用TA数组方便地求出Sum[k]的值,那么,我们就很容易方便地求出 第i个到第j个元素的各。

我们有如下的结果:

int sum(int k){

       int s= 0;

       while(k){

              s+= TA[k];

              k-= lowbit(k);

       }

}

这个函数显然返回的是Sum[k]的值,显然我们知道利用TA数组我们可以在log(k)的时间内求出Sum[k]的值。现在的问题是我们要快速地构造出TA数组,并且在较快的时间内能更新TA数组。也就是说,我们有如下两个问题:

问题一:构造TA数组

       任意给出一个k值,我们可以不断地用k-lowbit(k)代替k,直到为0,因此,我们有一个有限地递减序列K0,K1,K2…Kt,比如,当k是5的时候,我们的这个序列就是5、4、0。

根据上面的sum函数,我们有Sum[k]=TA[K0]+TA[K1]+…+TA[Kt],当然,TA[Kt]就是TA[0]也就是0。我们可以由TA数组得到Sum数组,那么,另外一方面,我们是否可以用TA数组反解出Sum数组呢?事实上可以,当然,在这里面我们需要定义一个二元的偏序关系<=,

如果给一个元素k,我们可以不断地用k-lowbit(k)代替k,直到为0。这样我们得到一个有限递减序列K0、K1、K2…Kt=0。对于此序列中任何两个元素Ki,Kj(i>j)我们定义二元偏序<=为Kj<=Ki,也就是说,从Ki出发,不断地用Ki-lowbit(Ki)代替Ki最后能得到Kj,则有关系Kj<=Ki。

利用这个定义,我们可以把Sum[k]=TA[K0]+TA[K1]+…+TA[Kt]改写为Sum[k]为所有的TA[j]的和,其中j满足偏序j<=k。根据Mobius反演率,我们有

TA[k]为所有的Sum[i]*u(i,k)的和,其中Sum[i]为前i个元素的和,u(i,k)为Mobius反演函数,剩下的任务是求出Mobius反演函数。

根据Mobius反演定义,我们有u(k,k)=1,u(i,k)为所有-u(i,j)的和,其中j在上术定义的偏序关系中满足i<=j<=k,且j不等于k。这样显然我们就有了u(k,k)=1,u(k-lowbit(k),k)=-1,其它的I,j有u(I,j)=0,这样我们就有了TA[k]=Sum[k]- Sum[k-lowbit(k)]。这个式子表明我们可以在O(n)的复杂度内构造出TA数组(因为我们可以在O(n)复杂度为构造出Sum数组,再利用Sum我们可以在O(n)的复杂度内构造出TA)。

问题二:当一个元素变化的时候如何快速更新TA数组。

       显然,如果从一个元素k开始,不断地用k-lowbit(k)代替k,我们得到一个序列K0(k)、K1、…Kt(0),则我们知道一个元素A[Ki]的变化只影响TA[Kj]其中j<=i的值。这根据TA的定义,我们知道是显然的。也就是说,当一个元素A[k]更新了的时候,我们更新TA也只需要在log(n)的时间内完成。算法如下:

void update(int inode, int iadd){

       while(inode<=n){

              TA[inode]+= iadd;

              inode+=lowbit(inode);

       }

}.

 

 

总结:

       这个东西也是我在做nyoj题目

(http://acm.nyist.net/JudgeOnline/problem.php?pid=116)的时候才想到的。当时看到那个Sum是用TA来求的时候我就立刻想到了用Mobius反演可以从Sum中反解出TA数组。当然,我比较菜,高手不要喷,谢谢。