前言
前面介绍了一维前缀和,二维前缀和是一维前缀和的在线升级,一维前缀和体现在数组,而二位前缀和体现在矩阵上。
一、什么是二维前缀和?
基于立在一维前缀和的基础上,现在所求是矩阵内一个任意的子矩阵的数的和,这样的问题我们就可以用二维前缀和进行求解。
二、二维前缀和讲解
引入
对于一个矩阵
例如:定义一个矩阵g[n][m]
const int n=3,m=4;
int g[n][m]={{1,5,6,8},{9,6,7,3},{5,3,2,4}};
g[n][m]
1 5 6 8
9 6 7 3
5 3 2 4
(1) 求矩阵(1,1)到(2,2)的和
(2) 求矩阵(0,1)到(1,3)的和
我们最容易想到的就是暴力枚举法
即:
问题(1) 6+7+3+2=18
问题(2) 5+6+8+6+7+3=35
其复杂度是O(nm) 如果矩阵短小还好 但当数据过大时这种办法就有些不时限了,所以我们引入二维的前缀和是其复杂度降低。
思路
如果我们将这个3*4的矩阵看成一个大长方形
对于问题(1)我们可以有另一种解法
<<凑活看吧第一次插入图片还没搞懂>>
(阴影为所求)
解法就是:所求阴影=大长方形-(1)-(2)+(3);
结合g[n][m]矩阵,我们进一步分析
找出矩阵g[1],g[2],g[3]
矩阵g[1]
1 5 6
9 6 7
5 3 2
矩阵g[2] 矩阵g[3]
1 1 5 6
9
5
问题(1) 就可解为:g[1]-g[2]-g[3]+1
现在的问题的就是该如何求矩阵的和了
我们此时引入二维前缀和sum[i][j]
sum[i][j]就是从(0,0)到(i,j)的矩阵和
这样就更好解了
问题(1)=sum[3][3]-sum[2][0]-sum[0][2]+sum[0][0];
由g[n][m] 可以求得sum[n][m]
sum[n][m]
1 6 12 20
10 21 34 45
15 29 44 59
问题(1)=44-15-12+1=18=6+7+3+2
将思维扩大,眼界放开
由对问题(1)的求解可以推断
(设sum[x1,y1][x2,y2]等同
从(x1,y1)到(x2,y2)的和)
sum[x1,y1][x2,y2]
=sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1]
代码实现如下(示例):
#include<bits/stdc++.h>
using namespace std;
const int n=3,m=4;
int g[n][m]={{1,5,6,8},{9,6,7,3},{5,3,2,4}};
int sum[n][m];
void pre_sum() //预处理生成二维前缀和
{
sum[0][0]=g[0][0]; //第一个
for(int i=1;i<n;i++)
sum[i][0]=sum[i-1][0]+g[i][0]; //第一列
for(int j=1;j<m;j++)
sum[0][j]=sum[0][j-1]+g[0][j]; //第一行
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
sum[i][j]=g[i][j]+sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];
}
int get_sum(int x1,int y1,int x2,int y2)
{
if(!x1&&!y1) return sum[x2][y2];
if(!x1) return sum[x2][y2]-sum[x2][y1-1];
if(!y1) return sum[x2][y2]-sum[x1-1][y2];
return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
int main()
{
pre_sum();
cout<<get_sum(1,1,2,2)<<""<<endl<<get_sum(0,1,1,3);
return 0;
}
解释
由于sum[][]是二维数组,
所以要先规定边界,再预处理sum[][]数组
void pre_sum() //预处理生成二维前缀和
{
sum[0][0]=g[0][0]; //第一个
for(int i=1;i<n;i++)
sum[i][0]=sum[i-1][0]+g[i][0]; //第一列
for(int j=1;j<m;j++)
sum[0][j]=sum[0][j-1]+g[0][j]; //第一行
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
sum[i][j]=g[i][j]+sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];
}
首先前sum[0][0]=g[0][0]肯定没问题吧
下面的两个for语句就是先行规定了sum的边界
sum规定完边界
1 6 12 20
10
15
规定二维前缀和的边界的方法和一维前缀和相同
然后根据边界去求sum[i][j]就相当简单了
for(int i=1;i<n;i++)
for(int j=1;j<m;j++)
sum[i][j]=g[i][j]+sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];
处理自定义函数get_sum()时先return了三种特殊情况
再普遍规律return也很容易理解,就不多说了(就是懒)
总结
二维前缀和和一维前缀和都不是很难理解,想象着图将图,用图去理解可能会更加容易一些。