二维树状数组可以实现在平面上的区域加、区域查询等操作。
区域修改
我们在一维时维护树状数组的区间操作时,对其进行了差分。类比一维的思想,我们在二维平面上也对树状数组差分。
我们来看二维的前缀和:
[sum(i,j)=sum(i-1,j)+sum(i,j-1)-sum(i-1,j-1)+a(i,j)
]
可以使二维差分数组为这样的形式:
[d(i,j)=a(i,j)-d(i-1,j)-d(i,j-1)+d(i-1,j-1)
]
(发现了吧,这其实就是)
比如我们有一下这个全为 (0) 的矩阵,那么给中间 (2 imes 3) 的矩阵差分后长成这样:(“o” 表示操作区域)
0 0 0 0 0 0 0 0 0 0
0 o o o 0 -> 0 +x 0 0 -x
0 o o o 0 0 0 0 0 0
0 0 0 0 0 0 -x 0 0 +x
这样我们就简单地完成了区域加的操作。
区域查询
根据差分数组的定义,我们不难发现,对于点 ((x,y)) ,它的二维前缀和就是:
[sum_{i=1}^xsum_{j=1}^ysum_{h=1}^isum_{k=1}^j d[h][k]
]
但我们类比一下一维树状数组的区间求和,我们亦可以统计每个 (d[h][k]) 出现的次数,我们就可以发现 (d[1][1]) 出现了 ((x imes y)) 次,(d[1][2]) 出现了 (x imes(y-1)) 次……(d[h][k]) 出现了 ((x-h+1) imes (y-k+1)) 次。
则原式整理得:
[sum_{i=1}^xsum_{j=1}^y d[i][j] imes (x-i+1) imes (y-j+1)
]
分解得:
[sum_{i=1}^xsum_{j=1}^y d[i][j] imes [(xy-xj+x)+(-yi+ij-i)+(y-j+1)]
]
最后得:
[sum_{i=1}^xsum_{j=1}^y d[i][j] imes (xy+x+y+1)-d[i][j] imes i(y+1)-d[i][j] imes j(x+1)+d[i][j] imes i imes j
]
根据我们最后分解出来的公式,我们需要维护四个数组 (d[i][j],d[i][j] imes i,d[i][j] imes j,d[i][j] imes i imes j) ,从而实现区间查询。
例题 — P4514 上帝造题的七分钟
即二维树状数组裸题。
$ exttt{code}$
#define Maxn 2050
int n,m,tree[Maxn][Maxn][4];
inline void add(int x,int y,int k)
{
int a=k,b=k*x,c=k*y,d=k*x*y;
for(int i=x;i<=n;i+=i&(-i))
for(int j=y;j<=m;j+=j&(-j))
{
tree[i][j][0]+=a;
tree[i][j][1]+=b;
tree[i][j][2]+=c;
tree[i][j][3]+=d;
}
}
inline int query(int x,int y)
{
int ret=0,a=x*y+x+y+1,b=y+1,c=x+1,d=1;
for(int i=x;i;i-=i&(-i))
for(int j=y;j;j-=j&(-j))
{
ret+=tree[i][j][0]*a;
ret-=tree[i][j][1]*b;
ret-=tree[i][j][2]*c;
ret+=tree[i][j][3]*d;
}
return ret;
}
if(修改) add(a,b,x),add(a,d+1,-x),add(c+1,b,-x),add(c+1,d+1,x);
else printf("%d
",query(c,d)-query(a-1,d)-query(c,b-1)+query(a-1,b-1));