二维树状数组学习笔记

1 首先从一维的开始,考虑最基础的单点修改,区间查询。树状数组模板1:这个是最基础的,就是定义的应用。

2再考虑区间修改,单点查询。树状数组模板2

我们可以想到,在对一段区间\(\;[l,r]\;\)进行修改时,可以将\(\;[1,r]\;\)加上\(\;k\),将\(\;[1,l-1]\;\)减去\(\;k\;\)。但这样修改,对于单点查询来说,似乎不是那么的方便,我们可能需要求出\(\;[1,x]\;\)\(\;[1,x-1]\;\)的前缀和,再做个差。好像...有点麻烦。

那么有什么更好的方法呢?——考虑差分

我们定义\(\;d_i=a_i-a_{i-1},\;a_0=0\;\)。那么每次修改,就只需要将\(\;d_l\;\)\(\;d_{r+1}\;\)进行修改,而单点查询即为\(\;\sum\limits_{i=1}^xd_i\)。直接进行前缀和查询即可。

3还有的就是区间修改,区间求和了。线段树模板1

我们跟着上面区间修改,单点查询的思路来,还是考虑差分。

修改的话,如同上面一样,只对差分数组\(\;d_{l-1}\;\)\(\;d_r\;\)进行修改,但有些许变动。

我们来看一个式子:

\[\sum\limits_{i=1}^r\sum\limits_{j=1}^id_j-\sum\limits_{i=1}^{l-1}\sum\limits_{j=1}^id_j \]

这个式子前一半表示的是区间\(\;[1,r]\;\)的和,后一半是区间\(\;[1,l-1]\;\)的和,二者做差即为所求的区间\(\;[l,r]\;\)的和。

尝试对其化简。

我们先只考虑前一半,即\(\;\sum\limits_{i=1}^r\sum\limits_{j=1}^id_j\)

发现\(\;d_1\;\)出现了\(\;r\;\)次,\(\;d_2\;\)出现了\(\;r-1\;\)\(\;\ldots\) \(d_x\;\)出现了\(\;r-x+1\;\)次。至此,我们发现式子可以变为:

\[\sum\limits_{i=1}^rd_i\times (r-i+1) \]

展开可得:

\[(r+1)\times \sum\limits_{i=1}^rd_i-\sum\limits_{i=1}^rd_i\times i \]

最终我们只需开两个树状数组来维护\(\;d_i\;\)\(\;d_i\times i\;\)即可。

修改:

il void add(int x,ll val)
{
	for(int i=x;i<=n;i+=lowbit(i))
		c1[i]+=val,c2[i]+=x*val;
}
il void real_add(int l,int r,int val)
{
	add(l,val);
	add(r+1,-val);
}

查询:

il ll ask(int x)
{
  ll ans=0;
  for(int i=x;i;i-=lowbit(i))
  	ans+=c1[i]*(x+1)-c2[i];
  return ans;
}
il ll real_ask(int l,int r)
{
  return ask(r)-ask(l-1);
}

4接下来考虑单点修改,区间查询。二维树状数组模板

我们只需将一维扩展到二维,代码只需进行一定的修改即可:

修改:

inline void add(int x,int y,int val)
{
	for(int i=x;i<=n;i+=lowbit(i))
		for(int j=y;j<=m;j+=lowbit(j))
			a[i][j]+=val;
}

查询:

inline int ask(int x,int y)
{
	int ans=0;
	for(int i=x;i;i-=lowbit(i))
		for(int j=y;j;j-=lowbit(j))
			asn+=a[i][j];
	return ans;
}

5最后是区间修改,区间查询。上帝造题的七分钟

回想一下,我们是怎么进行一维的区间修改的——差分。差分数组\(\;\sum\limits_{i=1}^nd_i=a_i\;\)。再想一下差分数组是如何求出来的。

我们来看两个式子:

\[sum_i=sum_{i-1}+a_i \]

\[d_i=a_i-a_{i-1} \]

应该能发现什么吧——差分数组的计算有点像前缀和计算的逆运算。所以,我们可以仿照二维数组的前缀和公式来推出二维数组的差分公式。

\[sum_{ij}=sum_{{i-1}j}+sum_{i{j-1}}-sum_{{i-1}{j-1}} \]

\[d_{ij}=a_{ij}-a_{{i-1}j}-a_{i{j-1}}+a_{{i-1}{j-1}} \]

在回想一下,我们在进行一维数组的差分修改时是如何修改的——再将其扩展到二维。

当我们要修改一个二维区间 \(\;(x_1,y_1)\ \ (x_2,y_2)\;\) 所组成的矩形时,只需修改\(\;(x_1,y_1)\ \ (x_1,y_2+1)\ \ (x_2+1,y_1)\ \ (x_2+1,y_2+1)\;\)四个点。那么,如何修改呢?

我们来看一个式子:

\[\sum\limits_{i=1}^x\sum\limits_{j=1}^y\sum\limits_{p=1}^i\sum\limits_{q=1}^jd_{pq} \]

我们发现,\(\;d_{11}\;\) 出现了\(\;x\times y\;\)次,\(\;d_{12}\;\)出现了 \(\;x\times {(y-1)}\;\)\(\;\ldots\) \(\;d_{pq}\;\)出现了\(\;{(x-p+1)}\times {(y-q+1)}\;\)次。

我们将式子展开,可得到

\[\sum\limits_{i=1}^x\sum\limits_{j=1}^y{d_{ij}\times (x-i+1)\times (y-j+1)} \]

再次展开

\[{(x+1)\times (y+1)\times \sum\limits_{i=1}^x\sum\limits_{j=1}^yd{ij}}-{(y+1)\times \sum\limits_{i=1}^x\sum\limits_{j=1}^y{d{ij}\times i}}-{(x+1)\times \sum\limits_{i=1}^x\sum\limits_{j=1}^y{d{ij}\times j}}+{\sum\limits_{i=1}^x\sum\limits_{j=1}^y{d{ij}\times i\times j}} \]

那么,我们就只需开四个树状数组来分别维护\(d_{ij}\quad d_{ij}\times i\quad d{ij}\times j\quad d{ij}\times i\times j\)即可。

区间查询就是\(\;sum_{{x_2}{y_2}}-sum_{{x_2}{y_1-1}}-sum_{{x_1-1}{y_2}}+sum_{{x_1-1}{y_1-1}}\)

完整代码:

//2020.11.4
//二维树状数组,区间修改,区间查询
#include<bits/stdc++.h>
using namespace std;
const int maxn=2050;
#define NEKO puts("NEKO")
#define il inline
#define vocaloid(v) (v>='0'&&v<='9')
il int read()
{
   int x=0,flag=1;char v=getchar();
   while(!vocaloid(v)) {if(v=='-') flag=-1;v=getchar();}
   while(vocaloid(v)) {x=(x<<1)+(x<<3)+v-'0';v=getchar();}
   return x*flag;
}
il int lowbit(int x) {return x&(-x);}
int n,m,c1[maxn][maxn],c2[maxn][maxn],c3[maxn][maxn],c4[maxn][maxn];
char ch;int X1,X2,Y1,Y2,val;
il void add(int x,int y,int val)
{
   for(int i=x;i<=n;i+=lowbit(i))
   	for(int j=y;j<=m;j+=lowbit(j))
   	{
   		c1[i][j]+=val;
   		c2[i][j]+=val*x;
   		c3[i][j]+=val*y;
   		c4[i][j]+=val*x*y;
   	} 
}
il void real_add(int X1,int Y1,int X2,int Y2,int val)
{
   add(X1,Y1,val);
   add(X1,Y2+1,-val);
   add(X2+1,Y1,-val);
   add(X2+1,Y2+1,val);
}
il int ask(int x,int y)
{	
   int ans=0;
   for(int i=x;i>0;i-=lowbit(i))
   	for(int j=y;j>0;j-=lowbit(j))
   		ans+=(x+1)*(y+1)*c1[i][j]-(y+1)*c2[i][j]-(x+1)*c3[i][j]+c4[i][j];
   return ans;
}
il int real_ask(int X1,int Y1,int X2,int Y2)
{
   return ask(X2,Y2)-ask(X2,Y1-1)-ask(X1-1,Y2)+ask(X1-1,Y1-1);
}
int main()
{
   cin>>ch;n=read(),m=read();
   while(cin>>ch)
   {
   	if(ch=='L')
   	{
   		X1=read(),Y1=read(),X2=read(),Y2=read(),val=read();
   		real_add(X1,Y1,X2,Y2,val);
   	}
   	if(ch=='k')
   	{
   		X1=read(),Y1=read(),X2=read(),Y2=read();
   		printf("%d\n",real_ask(X1,Y1,X2,Y2));
   	}
   }
   return 0;
}

\(PS\) 部分内容参考自以下几篇博客:

Lv1_kangdi——二维树状数组总结及模板

fmj_123——luogu-P4514-二维树状数组

十分感谢他们的博客对我学习此内容的帮助。

posted @ 2020-12-04 16:51  初雫  阅读(85)  评论(0编辑  收藏  举报