一、介绍:
【一维前缀和】
一维前缀和顾名思义
就是一维的前缀和
前缀和是什么呢?
前缀和就是到目前为止全部的和是多少
一维就是单纯的一串数
他的前缀和就成了一维前缀和
【举例】
1 2 3 4 5 6
他的前缀和依次就是 1 3 6 10 15 21
i位置上的前缀和就是从第一个数到第i
个数全部数的和
这样就很显然的知道了什么是一维前缀和了吧?
【二维前缀和是什么】
二维前缀和顾名思义
就是二维的前缀和
二维很显然了
有x轴和y轴也就是一个面
这很显然
那二维前缀和中一个f[i][j]表示的意思就是
以(1,1)为左上角以(i,j)为右下角这个矩阵里面数的和
如图
f[i][j]表示的就是图中红色的部分
【二维前缀和怎么求】
这里先不说,先假设自己知道了每f[i][j]
二维前缀和是多少
(这个锅我背,因为一开始设计的问题,导致先解释的求矩阵值而没有解释求f[i][j]
的值,因这两个很相似所以说一个就过了,先说了求矩阵那这里再说求点就有点重复了我嫌麻烦,所二维前缀和点的值就在最后说一下吧,在已知求矩阵的情况下)
【二维前缀和求矩阵元素和】
二维前缀和可以用来干什么呢?
一维前缀和你可以用来O(1)求某个点的值
那么类比一下
二维前缀和也是可以用来求某个矩阵的值的
但是怎么来求呢?
就如图中
知道了两个点的位置和他们的二维前缀和
图中红色是左上角的那个点的二维前缀和
红色+黄色部分是右下角的那个点的二维前缀和
是不是可以用这个来求出他们之间的矩阵的和呢?
也就是这一部分:
图中黑色的部分就是我们要求的那个矩阵和
看到这里yy一下
是不是可以通过某些奇怪的方法求出黑色部分是多少?
D点表示的二维前缀和值是红色部分+两个黄色部分+黑色部分
A点表示的是红色部分
B点表示的是上面的黄色部分+红色部分
C点表示的是下面的黄色部分+红色部分
这样是不是发现有什么神奇的东西快要出现了
这里面只有D的前缀和里面包括黑色部分
只要减去D里面的哪两个黄色部分和红色部分是不是就剩下了我们要求的黑色部分了?
那怎么减去呢?
可以这样:
D - B - C + A
这就是二维前缀和最重要的部分了
化成二维数组的形式就是这样的
f[i][j]−f[i−1][j]−f[i][j−1]+f[i−1][j−1]
【为什么上文成立】
继续看上面那张图
由D-B-C+A到方程式这个很显然所以就不多说了
只要证明出D-B-C+A是正确的那就没有问题了
这个可以化成:
红色部分+上面的黄色部分+下面的黄色部分+黑色部分-上面的黄色部分-红色部分-下面的黄色部分-红色部分+红色部分
这样是不是很巧妙的就只剩下了黑色部分
所以成立
【补充 —— 二维前缀和怎么求】
这个可以类比上面求矩阵的思想
只是这个矩阵的右下角是(i,j),左上角也是(i,j)
就是一个11的矩阵
所以也是很好求的
但是上面是已知D,A,B,C求黑色部分
这里你只知道A,B,C和黑色部分
因为是一个11的矩阵吗
所以黑色部分就只有一个元素也就是(i,j)坐标上的那个元素值
所以就可以个加法变减法,减法变加法一个性质的
通过A,B,C和黑色部分来求出D
D点表示的二维前缀和值是红色部分+两个黄色部分+黑色部分
A点表示的是红色部分
B点表示的是上面的黄色部分+红色部分
C点表示的是下面的黄色部分+红色部分
所以D就可以等于B+C-D+黑色部分:
上面的黄色部分+红色部分+下面的黄色部分+红色部分-红色部分+黑色部分
=上面的黄色部分+红色部分+下面的黄色部分+黑色部分
刚好等于D
方程式为
f[i][j]=f[i−1][j]+f[i][j−1]−f[i−1][j−1]+a[i][j]
二、模板
//查询某个矩阵里面数的总和
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1010;
int a[maxn][maxn],sum[maxn][maxn];
int main(){
int n,m,s;
cin>>n>>m;//输入原矩阵
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];//二维前缀和
}
}
int k;
cin>>k;//k次询问
while(k--){
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;//两个顶点坐标
cout<<sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]<<endl;//两个顶点之间的矩形的面积
}
return 0;
}
三、例题:
数字矩阵
描述
给出一个nxn的数字矩阵,要求计算这个数字矩阵中是否存在一个子矩阵,要求这个子矩阵的行和列的长度相同,同时这个子矩阵内所有数的和为s。如果存在,输出满足条件的矩阵的最小的边长,否则输出-1.
输入
首先,输入两个数字n和s,分别表示这个矩阵的大小以及一个给定的数字s(0<n<=1000,s<=1e9)
然后输入nn行,每行nn个数字,表示这个矩阵。数据保证矩阵中每个数字都在[1,1e9]
输出
如果存在满足题目要求的子矩阵,那么请输出所有满足的子矩阵中最小的矩阵的边长,否则输出-1.
输入样例 1
3 4 1 1 3 1 1 4 1 2 3
输出样例 1
1
输入样例 2
3 100 1 1 3 1 1 4 1 2 3
输出样例 2
-1
提示
样例1解释,给出的矩阵中存在两个满足条件的子矩阵,一个是长度为2的全1的方阵,另一个是长度为1的全4的方阵,这两个方阵的所有的数字的和都是4,显然长度为1的方阵就是我们要找的,答案输出1.
样例2解释,题目中不存在满足条件的方阵,所以输出-1
思路:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[1010][1010],sum[1010][1010];
int n,s;
int main(){
cin>>n>>s;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
}
}
int ans=0x3f3f3f3f;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
int l=0,r=min(i,j),mid;
while(l<=r){
mid=(l+r)>>1;
ll total = sum[i][j]-sum[i-mid][j]-sum[i][j-mid]+sum[i-mid][j-mid];
if(total>s){
r=mid-1;
}
else if(total<s){
l=mid+1;
}
else{
ans = min(ans,mid);
break;
}
}
}
}
if(ans == 0x3f3f3f3f) cout<<-1<<endl;
else cout<<ans<<endl;
return 0;
}