链接:这里
AC CODE
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
int n, k, m;
ll ans;
int maxm = -inf;
int a[505][505];
int main(){
cin >> 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];
}
}
for(int i = 1; i <= n; ++i){
for(int j = i; j <= n; ++j){
for(int l = 1, r = 1, sum = 0; r <= m; ++r){
sum += a[j][r] - a[i - 1][r];
while(sum > k){
sum -= a[j][l] - a[i - 1][l];
l++;
}
ans += r - l + 1;
}
}
}
cout << ans << endl;
return 0;
}
解题报告
题意解析
本题需要我们在一个数字矩阵里找出总和不大于k
的子矩阵数目。
思路
描述一个矩阵需要找到矩阵四个角的位置,我们直接用暴力枚举(某一点的x、y和长、宽;也可以理解为上下左右四个边界),时间复杂度是o(n4),接着把矩阵里面的数据全部加起来,比较计数。数据最大为500,显然会TLE。此法可过30%数据。
30% CODE
略
如果用二维前缀和优化矩阵,可以使查询时间变为o(1),但是依然要枚举四次。此法可过70%数据。
70% CODE
略
那有没有办法优化掉枚举呢?当然:
我们依然枚举上下边界,然后用一维前缀和处理单列的矩阵,这样一个二维的数据统计就变为了一维,接着用双指针处理一维的序列(即处理下边界的一维数组),先让右指针r
一直走到最右位(条件是矩阵数字和小于等于k
,那么最右位应该是合法矩阵的右边界 + 1),走的每一步如果合法,答案ans
加上最大矩阵的各子矩阵数目总和(一个集合有多少子集合问题,拎出来一个点然后分别与其他点进行匹配,有r - l
个,由于本身也算子集所以 + 1即为r - l + 1
);不合法就移动左指针使之合法。
此方法AC(抄的y总思路,他太强了,我是菜逼)。
AC CODE
#include <cstdio>
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#define inf 0x3f3f3f3f
#define ll long long
using namespace std;
int n, k, m;
ll ans;
int maxm = -inf;
int a[505][505];
int main(){
cin >> 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];
}
}
for(int i = 1; i <= n; ++i){
for(int j = i; j <= n; ++j){
for(int l = 1, r = 1, sum = 0; r <= m; ++r){
sum += a[j][r] - a[i - 1][r];
while(sum > k){
sum -= a[j][l] - a[i - 1][l];
l++;
}
ans += r - l + 1;
}
}
}
cout << ans << endl;
return 0;
}