题目描述
给定一个 N × M 的矩阵 A,请你统计有多少个子矩阵 (最小 1 × 1,最大 N × M) 满足子矩阵中所有数的和不超过给定的整数 K?
输入格式
第一行包含三个整数 N, M 和 K.
之后 N 行每行包含 M 个整数,代表矩阵 A.
输出格式
一个整数代表答案。
样例输入
3 4 10
1 2 3 4
5 6 7 8
9 10 11 12
样例输出
19
提示
满足条件的子矩阵一共有 19,包含:
大小为 1 × 1 的有 10 个。
大小为 1 × 2 的有 3 个。
大小为 1 × 3 的有 2 个。
大小为 1 × 4 的有 1 个。
大小为 2 × 1 的有 3 个。
对于 30% 的数据,N, M ≤ 20. 对于 70% 的数据,N, M ≤ 100.
对于 100% 的数据,1 ≤ N, M ≤ 500; 0 ≤ Ai j ≤ 1000; 1 ≤ K ≤ 250000000.
//首先,注意子矩阵是输入进数据的一部分
//输入进的数据位置是不能变的,是在这个固定的矩阵中选取其中小的矩阵!
//而不是输入进入数据然后随机排列!
//本文以示例数据作为参考
#include<stdio.h>
#include<stdlib.h>
int s[505][505];
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&s[i][j]);
s[i][j]+=s[i-1][j];
//双循环插入数据,
//加上前面的是因为相当于选取上下的矩阵使用,
//每一次都加上前面行数据的总和.
//每一个对应行数的数组数据都是当前行加前面所有行数的总和
//前缀和的思想
//这么写的好处就是不会超时,因为选中想要的数据都可以通过加减的运算直接得出
//我就不需要自己开n个循环自己去截取,这样会显得很麻烦而且必定超时
}
}
long long int res=0;//长整型防止数据过大溢出
//long long 是其简写;
for(int i=1;i<=n;i++)//选定截断的子矩阵范围
{
for(int j=i;j<=n;j++)//选定所需的子矩阵范围
//条件注意看清楚不要乱写!
//两个循环就相当于,先选定第一行开始选取行数,然后跟同时选第一二,第一二三,.....行一起选择
//(因为前面的数组每次都把前面数组的数据都加了起来,所以每次下一行的数组的数据都是前面
//行对应列的数据之和)
//然后再从第二行开始,跟着同时选二三,二三四,.....行一起
{
for(int left=1,right=1,sum=0;right<=m;right++)//是<=!
//每一轮sum都会重置一次,sum的范围只在单次选定的行数中
//right++既是推进遍历,又是一种默认,只要这层left不增
//就相当于这个数据可行,所以就要加1,而如果left增加
//说明当前sum太大,我就要舍弃最左边的数,要看当前数有多大,
//来决定减少最左边数据的多少,即left增多少
//right记录增加,left记录舍去,sum作为判断是否能同时选取多少个数,res为最终多少个矩阵
//原理就是选取和舍去,如从1开始,满足条件,记为1,然后加上2同样不超过,所以继续记录,此时res等于三是因为,这两个数据现在是同时可以选中,因此分别
//为单独的1和2,以及1和2一起成为一个矩阵的三种情况,后面的3,4也是选取不同子矩阵成为十种。
//举例如1,2,3中可以有1,2/2,3/1,2,3三种(注意要连续才为子矩阵)
//而从第二行开始就是同时选取1和5这两个数据和后面的2、6尝试连结,没有超过k就选取新的子矩阵,超过就删去1、5,剩下2、6,继续往下连结,重复操作
//后面第三行就是1,5,9连在一起尝试连结,以此类推
//而后面从第二行单独数据开始时后面同样重复,只是不再选取上一行的数据,从而达到全面查找矩阵的目的,如5,9绑在一起,参考图片。
{
sum+=s[j][right]-s[i-1][right];
//选取子矩阵大小
while(sum>k)
{
sum-=s[j][left]-s[i-1][left];//这里是去掉最左边无用的数据
left++;
//减少的个数增加
}
// res+=right-left+1;可读性差
res+=right-(left-1);
//这里的加1其实是因为下标问题,因为要用到left作下标,0又没有存放数据只有1开始才有
//因此不得不给left赋值上1,如果从0开始放数据,其实是res+=right-left;
//而更清楚的展示从1开始应该是res+=right - (left - 1);才更准确
//(开始的时候不存在舍去的数据)
//至于为什么就是找规律
//一开始从1,2,3,4
//得到的结果就有1,3,6,10,显然就是加下一个n即n+1的结果
//left是不可能会比right大的
//因为right就相当于在右边找数据,left相当于判断是否应该删除
//如果sum>k,那么left就会选中在sum中的最左边的数据删除
//一直到sum<=k为止,再退出while增加该情况.
//最坏的情况也是当前right增加的这个结果,一旦这个结果也被删除
//那么sum=0,因此最差也是res+=0,不可能会出现倒减
//而删除就是减少选取新的子矩阵的情况,意思就是有数据不能在这个排列中了
//要删去,根据子矩阵的选取,也就是删除最左边的,也只能舍去最左边的
//(子矩阵需要不间断)
//总结: left和right相当于划定边界,在这之间范围内的数可以选取的子矩阵。
// right一直往前走left紧跟着。只要sum大于k,left也往右移缩小范围,left和right都是两用,下标和划定数的数量范围
//right一直都是往前走的,left也有可能会删除,所以确保了每次选取的数都是新的,因此每一轮都要加上right-(left-1);
//
}
}
}
printf("%lld",res);
return 0;
}