T1 黑白格
题意:
给定一个n * m 的01网格。求最小包含至少k个1的矩形面积。
方法1: 二维前缀和 O(n^4)
思路:
首先枚举出矩形的左上端点和右下端点,然后使用二维前缀和来对这个矩形中的1的个数进行计数。二维前缀和的使用方法就不在这里赘述。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int s[N][N];
char t[N][N];
int get(int x1, int y1, int x2, int y2){
return s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
}
int main(){
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= n; i ++)cin >> t[i] + 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
int a = t[i][j] - '0';
s[i][j] = a + s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];
}
}
int ans = 0;
for(int x1 = 1; x1 <= n; x1 ++){
for(int y1 = 1; y1 <= m; y1 ++){
for(int x2 = x1; x2 <= n; x2 ++){
for(int y2 = y1; y2 <= m; y2 ++){
if(get(x1, y1, x2, y2) >= k){
if(ans == 0){
ans = (x2 - x1 + 1) * (y2 - y1 + 1);
}
else ans = min(ans, (x2 - x1 + 1) * (y2 - y1 + 1));
}
}
}
}
}
cout << ans << endl;
return 0;
}
方法2 : 一维前缀和+二分 O(n^3 * log(n))
思路:
首先确定这个矩形的左右边界,然后再枚举下边界,也就是矩形的四条边我们只暴力枚举三条,然后在下边界枚举的过程中,维护一个数组,这个数组中存储的是每次新加一行后的值,因为这个矩阵中只存在0和1,我们可以保证当从第一层枚举到第i层的时候,前i层的和一定是非递减的。所以就满足了二分查找上边界的要求。
我们在新增一行的时候采用一维前缀和获取这一行的和。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int s[N][N];
char t[N][N];
int main(){
int n, m, k;
cin >> n >> m >> k;
for(int i = 1; i <= n; i ++)cin >> t[i] + 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
int a = t[i][j] - '0';
s[i][j] = s[i][j - 1] + a;
// 主席要记录行前缀和,用来快速求出规定左右边界中一行的和
}
}
int ans = 0;
for(int y1 = 1; y1 <= m; y1 ++){
for(int y2 = y1; y2 <= m; y2 ++){
int now = 0; // 存储前x2 行的和
vector<int> num;// 用来存储每添加一行后的前缀和的数据
for(int x2 = 1; x2 <= n; x2 ++){
now += s[x2][y2] - s[x2][y1 - 1];
//新增一行
num.push_back(now);
//添加到数组中
if(now >= k){
if(ans == 0)ans = x2 * (y2 - y1 + 1);
else ans = min(ans,x2 * (y2 - y1 + 1));
int l = 0, r = x2 - 1;
//二分的原理:找到num中最大的数满足 now- num[]大于等于k
//因为是找到最晚出现 所以要用下面这个二分模板
while(l < r){
int mid = l + r + 1 >> 1;
if(now - num[mid] >= k)l = mid;
else r = mid - 1;
}
//这里有一个细节 这个l代表的是数组中的位置,l+1才是他的真实行
//但如果我在第l+1行满足条件,那么我的结果行应该是l+2.
// 可以参照具体样例进行理解,如果我now - 第一行的才大于等于k。那么这个矩阵范围应该是 第二行和第五行。
// 所以他的宽应该是 x2 - (l + 2) + 1) 也就是 x2 - l - 1;
if(now - num[l] >= k){
if(ans == 0) ans = (y2 - y1 + 1) * (x2 - l - 1);
ans = min(ans, (y2 - y1 + 1) * (x2 - l -1 ));
}
}
}
}
}
cout << ans << endl;
return 0;
}