题目如上,很明显,这题需要用到二维前缀和,由于子矩阵的大小未知,所以如果用暴力枚举四个边界(上下左右),时间复杂度就为O(n^4),一定会超时,所以要想一个方法降低时间复杂度,这题我们可以用双指针
1、先设置两个变量 i,j 分别枚举左右边界
2、设置两个指针start和end枚举上下边界,其中start为慢指针,end为快指针(start<=end)
3、如果得到的子矩阵中的数的和大于k,则慢指针start前进,这样子矩阵的和就一定不会增加了
4、直到子矩阵的和小于等于k,start不再前进了,此时的宽度为j-i+1的子矩阵个数为end-start+1,如果start>end,就不做操作,直接进入下一层循环
对于第4步,下面说几个比较难理解的点:
1、为什么是end-start+1个呢?这些子矩阵的宽度是固定的,关键是长度,长度分别为1,2,...,end-start+1(上下边界也分别为一行),所以一共有end-start+1个子矩阵
2、为什么只占一行的矩阵不算进去呢?,因为在i,j固定的时候,我们枚举的是end,每一次end都会往下走,而start会保持在当前的位置,如果下一行满足的话,在循环中的后面就会把从该子矩阵第二行开始的只占一行的子矩阵算在内了,如果我们现在就算的话,就会重复计算了
C++代码如下:
#include<iostream>
using namespace std;
const int N=510;
int a[N][N];
int n,m,k;
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];//计算前缀和
}
long long sum=0;//个数可能会爆int,所以用long long存储
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++){
for(int st=1,ed=1;ed<=n;ed++){
//子矩阵坐标为:左上(st,i),左下(ed,i),右上(st,j),右下(ed,j)
while(st<=ed&&a[ed][j]-a[ed][i-1]-a[st-1][j]+a[st-1][i-1]>k)st++;
if(st<=ed)sum+=ed-st+1;
}
}
printf("%lld\n",sum);
return 0;
}