二维树状数组学习笔记

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};)进行修改,而单点查询即为(;sumlimits_{i=1}^xd_i)。直接进行前缀和查询即可。

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

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

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

我们来看一个式子:

[sumlimits_{i=1}^rsumlimits_{j=1}^id_j-sumlimits_{i=1}^{l-1}sumlimits_{j=1}^id_j ]

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

尝试对其化简。

我们先只考虑前一半,即(;sumlimits_{i=1}^rsumlimits_{j=1}^id_j)

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

[sumlimits_{i=1}^rd_i imes (r-i+1) ]

展开可得:

[(r+1) imes sumlimits_{i=1}^rd_i-sumlimits_{i=1}^rd_i imes i ]

最终我们只需开两个树状数组来维护(;d_i;)(;d_i imes 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最后是区间修改,区间查询。上帝造题的七分钟

回想一下,我们是怎么进行一维的区间修改的——差分。差分数组(;sumlimits_{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);)四个点。那么,如何修改呢?

我们来看一个式子:

[sumlimits_{i=1}^xsumlimits_{j=1}^ysumlimits_{p=1}^isumlimits_{q=1}^jd_{pq} ]

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

我们将式子展开,可得到

[sumlimits_{i=1}^xsumlimits_{j=1}^y{d_{ij} imes (x-i+1) imes (y-j+1)} ]

再次展开

[{(x+1) imes (y+1) imes sumlimits_{i=1}^xsumlimits_{j=1}^yd{ij}}-{(y+1) imes sumlimits_{i=1}^xsumlimits_{j=1}^y{d{ij} imes i}}-{(x+1) imes sumlimits_{i=1}^xsumlimits_{j=1}^y{d{ij} imes j}}+{sumlimits_{i=1}^xsumlimits_{j=1}^y{d{ij} imes i imes j}} ]

那么,我们就只需开四个树状数组来分别维护(d_{ij}quad d_{ij} imes iquad d{ij} imes jquad d{ij} imes i imes 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
",real_ask(X1,Y1,X2,Y2));
   	}
   }
   return 0;
}

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

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

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

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

原文地址:https://www.cnblogs.com/MIKU5201314/p/14086661.html