二维前缀和详解

这几天在打比赛时遇到了二维前缀和,看了一下深有体会,发一篇详解。
首先,什么是前缀和?一个数列,我们要计算某个区间内的和,该怎么做呢?正所谓暴力出奇迹,这一个也可以,我们暴力枚举每一个区间内的数并且相加,可是这个是O(n)的时间复杂度,不要小看这个线性,可如果在DP里的话这相当于加了一次方,卡你非常简单,下面讲一下O(n)的预处理,O(1)的计算,你可能会说这个也是线性,有差吗?当然有差,这个是需处理,不会像暴力一样在DP中使DP的时间复杂度加一次方而是直接加上O(n)。下面讲一下做法,前缀和,顾名思义就是第几个数之前的数的和,我们用DP来预处理,我们定义状态DP[i]表示到第i个数(包括它)为止前面所有数的和,从而得出状态转移方程DP[i]=DP[i-1]+num[i],num[i]表示数组中第i个数,这样要计算闭区间i,j(闭区间就是指i<=x<=j,x就是区间里的数)的和就是DP[j]-DP[i-1],这个画图理解如下:二维前缀和详解_第1张图片
下面我们讲一下什么是二维前缀和,建立在一维前缀和之上,我们要求一个矩阵内一个任意的子矩阵的数的和,我们就可以用二维前缀和,我们还是用DP来预处理,状态和一维前缀和差不多,只不过我们多加了一维,DP[i][j]表示(1,1)这个点与(i,j)这个点两个点分别为左上角和右下角所组成的矩阵内的数的和,好好想一下状态转移方程,DP[i][j]=DP[i-1][j]+DP[i][j-1]-DP[i-1][j-1]+map[i][j],怎么来的呢?我们画一下图就知道了。
在这里插入图片描述
这张图就知道了(i,j)可以由(i-1,j)和(i,j-1)两块构成,不过要注意两个点,1、有一块矩阵我们重复加了,也就是(i-1,j-1)这一块,所以我们要减去它。2、我们这个矩阵是不完整的,由图可知我们还有一块深蓝色的没有加,也就是(i,j)这一点,所以我们要再加上map[i][j]也就是题目给出的矩阵中这一格的数。这样我们就预处理完了,现在讲一下怎么通过我们的预处理从而快速地得出我们想要的任意子矩阵中的和,我们定义(x1,y1)为我们想要子矩阵的左上角,(x2,y2)为我们想要子矩阵的右下角,然后我们画图想一想。
二维前缀和详解_第2张图片
我们可以通过DP[x2][y2]来计算,我们通过图可以发现这个距离我们要的还差红色的部分看看怎么表示红色部分?我们可以分割成两块,分别是DP[x1][y2]和DP[x2][y1]我们发现有一块重复减了,所以我们再加上它即DP[x1][y1],有一点注意,因为画图和定义原因我们发现边界好像不对,我们来看看,我们定义的状态是整个矩阵包括边的和,而我们要求的也是要包括边的,所以我们要再改一下,把DP[x1][y2]和DP[x2][y1]和DP[x1][y1]分别改成DP[x1-1][y2]和DP[x2][y1-1]和DP[x1-1][y1-1]这样一减我们就可以得到自己想要的答案,整理可得公式,DP[x2][y2]-DP[x1-1][y2]-DP[x2][y1-1]+DP[x1-1][y1-1]这样我们就可以做到O(1)之内查询,很奇妙吧,我们看一下实现代码:

#include
#include
using namespace std;
int dp[2000][2000],map[2000][2000];
int main()
{
	int m,n,k;//所给的矩阵是n*m的,有k组查询 
	cin >>n>>m>>k;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin >>map[i][j];
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=n;i++)//预处理一波 
		for(int j=1;j<=m;j++)
			dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+map[i][j];
	for(int i=1;i<=k;i++)//接受查询 
	{
		int x1,x2,y1,y2;
		cin >>x1>>y1>>x2>>y2;
		cout <<(dp[x2][y2]+dp[x1-1][y1-1]-dp[x1-1][y2]-dp[x2][y1-1])<<endl;//O(1)查询 
	}
	return 0;
} 

这样二维前缀和就讲完了,下面给一个不错的例题:
模板题
给一下题解:弱鸡写的题解
希望这一篇算法解析对大家有帮助。

你可能感兴趣的:(算法,二维前缀和,一维前缀和,前缀和)