二维树状数组

二维树状数组可以实现在平面上的区域加、区域查询等操作。

区域修改

我们在一维时维护树状数组的区间操作时,对其进行了差分。类比一维的思想,我们在二维平面上也对树状数组差分。

我们来看二维的前缀和:

[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));

参考文章

原文地址:https://www.cnblogs.com/EricQian/p/15375842.html