树状数组

Posted noncontradiction

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树状数组相关的知识,希望对你有一定的参考价值。

主要解决的问题

对于n个数,有修改和查询操作

单点修改区间查询(对于第i个数增加或减少一个值,然后求一个区间的值,这个区间也可以是一个点)

区间修改单点查询 (对于一个区间都增加或减少一个值,然后求一个点的值,这类题我们让每个点先记录的是和前面值的差,那么一个点的前缀和就是这个点的值,对于修改一个区间,只需要这个区间最左面的点加上这个值,最后面+1那个点减去这个值,这样对于每个点的前缀和,只有这个区间里的点有改变,所以整体操作还是只修改了一个点)

 

代码量比线段树少很多,但是解决的问题也少很多。

其中0号点不能存值

a[i]存每个点的值,f[i]存树状数组每个点的值

Lowbiti=x&(-x)  是计算一个数最低位1所代表的值,例如lowbit(6)=2因为11010=2

 

操作有两个

Add(i,v)   i号点增加v

Su(i)      i个点的值求和

 

 

在树状数组中数组的每个点记录的并不是某个点的值,它记录的是某个点二进制下,去掉最低位1然后加1,一直到本个数之间的值,例如12号点,他的二进制是1100,他其实记录的是10011010,1011,1100的值的和

对于i点进行add操作,最初和更新后的每一个i=i+lowbit(i)&&i<=nf[i]都要增加值v

所以反过来看的话 a数组中1010增加v的话,f数组中1010,1100,10000.....都要增加v(省略号有多少,就要看整体的数到底有多少,n的大小)

因为对于n最多有lgn位,操作最多就是lgn次

 

su就是求前缀和,例如1100只能求出1000+1~1100的数,所以还差0+1~1000,而些数恰好在1000中,容易看出,只要求出每一个i=i-lowbit(i)&&i!=0f[i]值之和即可

因此也跟1的个数有关,所以操作也是lgn次

int lowbit(int x)

{

    return x&(-x);

}

void add(int x,int v)

{

    while(x<=n)

    {

        f[x]+=v;

        x+=lowbit(x);

    }

}

int su(int x)

{

    int sum=0;

    while(x)

    {

        sum+=f[x];

        x-=lowbit(x);

    }

    return sum;

}

 

树状数组还可以进行二维的修改的查询,例如对于一个矩阵要修改一个区间,然后查询一个点,就可以同样用前缀的思想,每个点到源点形成的小矩阵记录这个点a[i][j]的值

那么修改一个矩阵(x,y)(xx,yy),若0<x<=xx,0<y<=yy, 只需要用add函数把(x,y),(xx+1,yy+1)增加v,(x,yy+1),(xx+1,yy)减去v即可(1维数组改两个点,二维改4...

对于n*m的矩阵

void add(int x,int y,int v)

{

    int yy=y;

    while(x<=n)

    {

        y=yy;

        while(y<=m)

        {

           f[x][y]+=v;

           y+=lowbit(y);

        }

        x+=lowbit(x);

    }

}

 

以上是关于树状数组的主要内容,如果未能解决你的问题,请参考以下文章

树状数组的树状数组的经典操作

树状数组模板

数据结构之树状数组从零认识树状数组

树状数组 / 二维树状数组

浅谈树状数组

Bit的树状数组