二维树状数组
前置知识
树状数组(不会就学一下再来)
简介
因为树状数组可以非常简洁解决序列上的一些问题,所以考虑能否用树状数组解决矩阵(二维序列)的问题。
比较暴力的想法是对于每一横行建一个树状数组,再对每一列建一个树状数组统计答案。
但这样显然要\(n+m\)个树状数组,但是我们发现这些树状数组复用了一些节点,所以我们考虑不用真的建出横行的树状数组,直接用竖行的就可以储存所有信息。
用法
单点修改 区间查询
树状数组直接记和,查询就是二维区间和的式子,单点修改就是直接改,考虑会对哪些树状数组的点进行修改即可。
void add(int x,int y,int d)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
sh[i][j]+=d;
}
LL query(int x,int y)
{
LL ret=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
ret+=sh[i][j];
return ret;
}
区间修改 单点查询
区间修改就不能直接记和,记差分(二维差分为\(d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1]\),你会发现性质与一维差分相同)。
考虑一次修改会对差分数组带来什么影响。(对黄色区间区间加\(w\),差分数组四个红色点要修改)
所以\(a[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]\)。
树状数组代码与上面相同,主函数改一下修改即可
区间修改 区间查询
结合上两个即可。
区间修改用差分,但区间求和怎么办?
\[sum[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}\sum_{h=1}^{i}\sum_{k=1}^{j}d[h][k]
\]
\[sum[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j](x-i+1)(y-j+1)
\]
\[sum[i][j]=\sum_{i=1}^{x}\sum_{j=1}^{y}d[i][j]ij+d[i][j](xy+x+y+1)-d[i][j]i(y+1)-d[i][j]j(x+1)
\]
所以维护4个二维树状数组,分别存\(d[i][j]\),\(d[i][j]ij\),\(d[i][j]i\),\(d[i][j]j\)即可。
inline void modify(int id,int x,int y,int w)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
sh[id][i][j]+=w;
}
inline int query(int id,int x,int y)
{
int ans=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
ans+=sh[id][i][j];
return ans;
}
inline void modifyz(int a,int b,int zhi)
{
modify(0,a,b,zhi);modify(1,a,b,zhi*a*b);modify(2,a,b,zhi*a);modify(3,a,b,zhi*b);
}
inline int sum(int a,int b)
{
return query(0,a,b)*(a*b+a+b+1)+query(1,a,b)-query(2,a,b)*(b+1)-query(3,a,b)*(a+1);
}
例题
P4514 上帝造题的七分钟
板子题,实现上面的区间修改区间查询即可。(注意自己认真推一下修改和查询的位置,不要写错了)
#include <bits/stdc++.h>
using namespace std;
const int N=2050;
int n,m,sh[4][N][N];
char s;
int lowbit(int x){return x&(-x);}
inline void modify(int id,int x,int y,int w)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=m;j+=lowbit(j))
sh[id][i][j]+=w;
}
inline int query(int id,int x,int y)
{
int ans=0;
for(int i=x;i;i-=lowbit(i))
for(int j=y;j;j-=lowbit(j))
ans+=sh[id][i][j];
return ans;
}
inline void modifyz(int a,int b,int zhi)
{
modify(0,a,b,zhi);modify(1,a,b,zhi*a*b);modify(2,a,b,zhi*a);modify(3,a,b,zhi*b);
}
inline int sum(int a,int b)
{
return query(0,a,b)*(a*b+a+b+1)+query(1,a,b)-query(2,a,b)*(b+1)-query(3,a,b)*(a+1);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>s>>n>>m;
int a,b,c,d,zhi;
while(cin>>s>>a>>b>>c>>d)
{
if(s=='L')
{
cin>>zhi;
modifyz(a,b,zhi);modifyz(c+1,b,-zhi);modifyz(c+1,d+1,zhi);modifyz(a,d+1,-zhi);
}
else
{
int ans;
ans=sum(c,d)-sum(c,b-1)-sum(a-1,d)+sum(a-1,b-1);
cout<<ans<<'\n';
}
}
return 0;
}
Iahub and Xors
类似于与上面的区间修改,区间查询,但是这里不是加,而是异或。
把所有加减和乘变成异或即可。因为\(x \oplus x=0\),最后那个式子不关心具体的数值,只关心奇偶性,只有同奇偶才有贡献,所以多开两维记一下x,y奇偶性,只在奇偶性相同的之间统计答案即可。(也可以仿照上一个题一样写)
#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
#define int long long
int n,m,sh[2][2][N][N];
int lowbit(int x){return x&(-x);}
int query(int x,int y)
{
int res=0;
for(int i=x;i>0;i-=lowbit(i))
for(int j=y;j>0;j-=lowbit(j))
res^=sh[x%2][y%2][i][j];
return res;
}
void modify(int x,int y,int w)
{
for(int i=x;i<=n;i+=lowbit(i))
for(int j=y;j<=n;j+=lowbit(j))
sh[x%2][y%2][i][j]^=w;
return ;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int opt,xx1,xx2,yy1,yy2,w;
cin>>opt>>xx1>>yy1>>xx2>>yy2;
if(opt==1)
{
int ans1=query(xx2,yy2)^query(xx1-1,yy2)^query(xx1-1,yy1-1)^query(xx2,yy1-1);
cout<<ans1<<'\n';
}
else
{
cin>>w;
modify(xx1,yy1,w);
modify(xx2+1,yy1,w);
modify(xx1,yy2+1,w);
modify(xx2+1,yy2+1,w);
}
}
return 0;
}