二维树状数组可以实现在平面上的区域加、区域查询等操作。
区域修改
我们在一维时维护树状数组的区间操作时,对其进行了差分。类比一维的思想,我们在二维平面上也对树状数组差分。
我们来看二维的前缀和:
\[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));
参考文章