二维树状数组

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

区域修改

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

我们来看二维的前缀和:

\[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\times 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}^x\sum_{j=1}^y\sum_{h=1}^i\sum_{k=1}^j d[h][k] \]

但我们类比一下一维树状数组的区间求和,我们亦可以统计每个 \(d[h][k]\) 出现的次数,我们就可以发现 \(d[1][1]\) 出现了 \((x\times y)\) 次,\(d[1][2]\) 出现了 \(x\times(y-1)\) 次……\(d[h][k]\) 出现了 \((x-h+1)\times (y-k+1)\) 次。

则原式整理得:

\[\sum_{i=1}^x\sum_{j=1}^y d[i][j]\times (x-i+1)\times (y-j+1) \]

分解得:

\[\sum_{i=1}^x\sum_{j=1}^y d[i][j]\times [(xy-xj+x)+(-yi+ij-i)+(y-j+1)] \]

最后得:

\[\sum_{i=1}^x\sum_{j=1}^y d[i][j]\times (xy+x+y+1)-d[i][j]\times i(y+1)-d[i][j]\times j(x+1)+d[i][j]\times i\times j \]

根据我们最后分解出来的公式,我们需要维护四个数组 \(d[i][j],d[i][j]\times i,d[i][j]\times j,d[i][j]\times i\times j\) ,从而实现区间查询。

例题 — P4514 上帝造题的七分钟

即二维树状数组裸题。

$\texttt{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\n",query(c,d)-query(a-1,d)-query(c,b-1)+query(a-1,b-1));

参考文章

posted @ 2021-10-07 14:23  EricQian06  阅读(105)  评论(0编辑  收藏  举报