2036: [蓝桥杯2022初赛] 统计子矩阵
内存限制:256 MB 时间限制:1 S 标准输入输出
题目类型:传统 评测方式:文本比较 上传者:外部导入
提交:310 通过:74
题目描述
给定一个 N × M 的矩阵A,请你统计有多少个子矩阵(最小 1 × 1,最大 N × M) 满足:
子矩阵中所有数的和不超过给定的整数K?
输入格式
第一行包含三个整数N, M 和K.
之后 N 行每行包含 M 个整数,代表矩阵A.
30%的测试数据:1≤N,M≤20;
70%的测试数据:1≤N,M≤100;
100%的测试数据:1≤N,M≤500;0≤Aij≤1000;1≤K≤250000000。
输出格式
一个整数代表答案。
输入样例 复制
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 个。
可笑的是,第一次看到这个题,写了一个六层循环,吓死人!
对于这种二维矩阵,求和,可以用dp[i][j]来表示matr[0][0]到matr[i][j]的和,
这样能简便后面求和的步骤,减少两阶的时间复杂度,这是二维前缀和。
同时,一维前缀和怎么表示呢?dp[i][j]表示matr[0][j]到matr[i][j]的和,这只表示
第j列前面i行的和。所以这就是一维前缀和和二位前缀和的区别
对于递增的二维前缀和,如果要计算子矩阵个数等问题,可以先固定行号,再迭代列号进行遍历,这样可以根据递增的性质减少时间复杂度,实现三重循环。因为如果先固定左上角坐标,再固定右下角坐标,则会出现四重循环,即不能利用二维前缀和递增的性质。
/**对于这种二维矩阵,求和,可以用dp[i][j]来表示matr[0][0]到matr[i][j]的和,
这样能简便后面求和的步骤,减少两阶的时间复杂度,这是二维前缀和。
同时,一维前缀和怎么表示呢?dp[i][j]表示matr[0][j]到matr[i][j]的和,这只表示
第j列前面i行的和。所以这就是一维前缀和和二位前缀和的区别
*/
/**二维前缀和求解*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int dp[502][502]={0};
int main()
{
memset(dp,0,sizeof(dp));
int n,m,top;
scanf("%d%d%d",&n,&m,&top);
int val;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
scanf("%d",&val);
dp[i][j]=dp[i][j-1]+dp[i-1][j]-dp[i-1][j-1]+val;
}
long long sum=0; //sum还必须定义成long long,否则百分之二十答案错误
// for(int i=1;i<=n;++i) //四重循环明显超时,百度看的"双指针求解"
// {
// for(int j=1;j<=m;++j)
// {
// for(int k=i;k<=n;++k)
// {
// for(int h=j;h<=m;++h)
// {
// if(dp[k][h]-dp[k][j-1]-dp[i-1][h]+dp[i-1][j-1]<=top)
// sum+=1;
// else break;
// }
// }
// }
// }
//先固定行号,列号从一开始,col_l 从1开始递增,col_l最能加n次,但是上面这个四重循环,等价于
//将col_l要循环n^2次;
for(int i=1;i<=n;++i) //百度看的"双指针求解"
for(int j=i;j<=n;++j) /**现在才看明白,就是tow point*/
for(int col_l=1,col_r=1;col_r<=m;++col_r)
{
while(col_l<=col_r&&(dp[j][col_r]-dp[j][col_l-1]-dp[i-1][col_r]+dp[i-1][col_l-1])>top)
++col_l;
if(col_l<=col_r)
sum+=col_r-col_l+1;
}
cout << sum << endl;
return 0;
}
/**一维前缀和求解*/
/**一维前缀和求解*/
/**
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int dp[502][502]={0};
int main()
{
memset(dp,0,sizeof(dp));
int n,m,top;
scanf("%d%d%d",&n,&m,&top);
int val;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
scanf("%d",&dp[i][j]);
dp[i][j]+=dp[i-1][j];
};
long long result=0; //result还必须定义成long long,否则百分之二十答案错误
for(int i=1;i<=n;++i) //百度看的"双指针求解"
for(int j=i;j<=n;++j) //现在才看明白,就是tow point
for(int col_l=1,col_r=1,sum=0;col_r<=m;++col_r)
{
sum+=dp[j][col_r]-dp[i-1][col_r];
while(sum>top)
{
sum-=dp[j][col_l]-dp[i-1][col_l];
++col_l;
}
if(col_l<=col_r)
result+=col_r-col_l+1;
}
cout << result << endl;
return 0;
}
*/