树状数组2——更高深的树状数组

一、区间修改区间求和

这个东西可就不是把 单点查询区间修改 和 单点修改区间查询 合起来那么简单了,仔细想想,拿之前的办法都不咋地好做,那么怎么办呢,我们还是要用到差分数组,设原数组为a,差分数组为b,则b[1]=a[1],b[i]=a[i]-a[i-1]

那么区间修改我们就解决了,只需要log的时间即可,但是,这种做法对于区间求和就很弱了。我们首先看一下用它求前缀和是什么样的(只要知道怎么求前缀和那么区间求值也就可以用前缀和相减得到了):

比如要求a[1]~a[i]:

    (b[1])+(b[1]+b[2])+(b[1]+b[2]+b[3])+……+(b[1]+b[2]+……+b[i])

=b[1]*i+b[2]*(i-1)+……+b[i]*1

化简后我们得到的是这么个东西,但是显然的,这种b[1]*i,b[2]*(i-1)我们是无法用树状数组维护的,因为i是时刻在变化的,所以,我们再把这个式子搞一下:

=(b[1]+b[2]+……+b[i])*i-(b[1]*0+b[2]*1+b[3]*2+……+b[i]*(i-1))

然后!我们就发现,这个式子可以维护了!这个式子分成两部分:

(b[1]+b[2]+……+b[i])*i

这一部分也就是b的一个前缀和乘i,前缀和是可以维护的!

(b[1]*0+b[2]*1+b[3]*2+……+b[i]*(i-1))

可以发现,对于每个b[i],都只会乘一个固定的数——(i-1),所以,也是可以维护的!

那么,查询操作就解决了,用两个树状数组维护即可,那么修改操作时同时修改这两个树状数组即可。

代码如下:

#include <cstdio>
#include <cstring>

int n,m;
int tree[100010],tr[100010];//tree是差分数组b的树状数组,tr是b[i]*(i-1)的树状数组 
int lowbit(int x){return (-x)&x;}
void change(int *a,int x,int y)
{
    for(int i=x;i<=n;i+=lowbit(i))
    a[i]+=y;
}
int get(int *a,int x)
{
    int p=0;
    for(int i=x;i>=1;i-=lowbit(i))
    p+=a[i];
    return p;
}
void ch(int x,int y)//修改 
{
    change(tree,x,y);
    change(tr,x,y*(x-1));
}
int print(int x){return get(tree,x)*x-get(tr,x);}//查询,也就是(b[1]~b[i])*i  -  (b[1]*0+b[2]*1+...+b[i]*(i-1)) 

int main()
{
    scanf("%d %d",&n,&m);
    int l=0,r;
    memset(tree,0,sizeof(tree));
    memset(tr,0,sizeof(tr));
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&r);
        ch(i,r-l);//将每两个数的差放进去 
        l=r;
    }
    for(int i=1;i<=m;i++)
    {
        int id;
        scanf("%d",&id);
        if(id==1)
        {
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            ch(x,z);//差分数组的修改 
            ch(y+1,-z);
        }
        else
        {
            int x,y;
            scanf("%d %d",&x,&y);
            printf("%d\n",print(y)-print(x-1));//前缀和相减 
        }
    }
}

 

二、二维树状数组

(1)单点修改区间查询

其实,二维的树状数组还真就是加多一维而已,其他什么都没有变化,但是可能让人费解,怎么就只是多一维的事呢?

我们设这个矩阵的树状数组为tree[i][j]

j这一维是用来维护列的,那么自然地,i这一维就是用来维护行的啦,维护的方法都按树状数组的方法来维护,那么把他们组合起来之后,tree[i][j]表示的就是——在i节点管理的行中,j节点管理的列的和。

可能不大好理解,那么我就良心的上个图吧!

 

就比如这幅图,tree[4][6]所管理的范围就是图中黄色部分

这样应该懂了吧qwq

代码如下:

#include <cstdio>
#include <cstring>

int tree[1010][1010];
int n,m,k;
inline int lowbit(int x){return x&(-x);}
void add(int x,int y,int z)
{
    for(;x<=n;x+=lowbit(x))
    for(;y<=m;y+=lowbit(y))
    tree[x][y]+=z;
}
int sum(int x,int y)
{
	int p=0;
	for(;x>=1;x-=lowbit(x))
	for(;y>=1;y-=lowbit(y))
	p+=tree[x][y];
	return p;
}

int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		int x;
		scanf("%d",&x);
		add(i,j,x);
	}
	scanf("%d",&k);
	for(int p=1;p<=k;p++)
	{
		int id;
		scanf("%d",&id);
		if(id==0)//给点(x,y)+z
		{
			int x,y,z;
			scanf("%d %d %d",&x,&y,&z);
			add(x,y,z);
		}
		else//求(x,y)到(xx,yy)这个矩阵内的点的和
		{
			int x,y,xx,yy;
			scanf("%d %d %d %d",&x,&y,&xx,&yy);
			printf("%d",sum(xx,yy)-sum(xx,y-1)-sum(x-1,yy)+sum(x-1,y-1));
		}
	}
}

(2)区间修改单点查询

我们设d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1],d就是差分数组,虽然看起来可能比较奇怪,但是他依然满足这个性质:差分数组的前缀和表示这个点。所以,每次修改矩阵时,其实就只需要修改d[xx+1][yy+1],d[xx+1][y],d[x][yy+1],d[x][y]四个点即可,求点的话就用维护d值即可,写一棵改点求段的二维树状数组即可。

代码就懒得贴了。

(3)区间修改区间查询

类似的,依然使用上面的差分数组,考虑怎么维护矩阵的前缀和(注意是a数组的前缀和)。

因为  a[i][j]=\sum ^i _{i1=1} \sum ^j _{j1=1}d[i1][j1]  ,所以,如果我要求(1,1)到(x,y)的a数组的前缀和,显然,对于每个d[i][j],会被(i,j)到(x,y)区间内的点计算到,所以每个d[i][j]被计算的次数为(x-i+1)*(y-j+1),展开变成(x+1)*(y+1)-(x+1)*j-(y+1)*i+i*j,也就是说,我们需要维护(x+1)*(y+1)*d[i][j]-(x+1)*j*d[i][j]-(y+1)*i*d[i][j]+i*j*d[i][j]的值,因为xx和yy会根据不同询问而改变,所以我们实际上要维护的是d[i][j],j*d[i][j],i*d[i][j],i*j*d[i][j]的值,分开在四棵改点求段二维树状数组维护即可。

代码:

#include <cstdio>
#include <cstring>

int tr1[2100][2100],tr2[2100][2100],tr3[2100][2100],tr4[2100][2100];//tr1~4对应维护上面的d[i][j],j*d[i][j],i*d[i][j],i*j*d[i][j]
int n,m;
int lowbit(int x){return x&(-x);}
void change(int x,int y,int z)
{
    if(!x||!y)return;
    for(int i=x;i<=n;i+=lowbit(i))
    {
        for(int j=y;j<=m;j+=lowbit(j))
        {
            tr1[i][j]+=z;
            tr2[i][j]+=z*y;
            tr3[i][j]+=z*x;
            tr4[i][j]+=z*x*y;
        }
    }
}
int print(int x,int y)
{
    int ans=0;
    for(int i=x;i>=1;i-=lowbit(i))
    for(int j=y;j>=1;j-=lowbit(j))
    ans+=tr1[i][j]*(x+1)*(y+1)-tr2[i][j]*(x+1)-tr3[i][j]*(y+1)+tr4[i][j];
    return ans;
}

int main()
{
    scanf("X %d %d",&n,&m);
    memset(tr1,0,sizeof(tr1));
    memset(tr2,0,sizeof(tr2));
    memset(tr3,0,sizeof(tr3));
    memset(tr4,0,sizeof(tr4));
    char s[3];
    while(scanf("%s",s)!=EOF)
    {
        if(s[0]=='L')
        {
            int x,y,xx,yy,z;
            scanf("%d %d %d %d %d",&x,&y,&xx,&yy,&z);
            change(x,y,z);
            change(xx+1,yy+1,z);
            change(xx+1,y,-z);
            change(x,yy+1,-z);
        }
        else
        {
            int x,y,xx,yy;
            scanf("%d %d %d %d",&x,&y,&xx,&yy);
            printf("%d\n",print(xx,yy)-print(xx,y-1)-print(x-1,yy)+print(x-1,y-1));
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值