一、区间修改区间求和
这个东西可就不是把 单点查询区间修改 和 单点修改区间查询 合起来那么简单了,仔细想想,拿之前的办法都不咋地好做,那么怎么办呢,我们还是要用到差分数组,设原数组为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数组的前缀和)。
因为 ,所以,如果我要求(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));
}
}
}