分享一道自认为很好的题 ...
题目
【问题描述】
数据图为一个 n × m 的01矩阵,定义稠密01正方形为一个包含的0的个数不大于 f 的正方形。请找出该数据图中最大的01稠密正方形,并输出其边长。
【输入形式】
第一行包含两个正整数 n、m、f接下来 n 行,每行 m 个用空格间隔的‘0’或‘1’,表示给出的数据图
【输出形式】
输出为一行,包含一个正整数表示最大的01稠密正方形的边长
【样例输入】3 3 1
1 1 1
1 0 0
0 1 1
【样例输出】2
【样例说明】无
【评分标准】对于 30%的数据,保证1≤ n ≤50,1≤ m ≤50
对于 70%的数据,保证1≤ n ≤1500,1≤ m ≤1500
对于100%的数据,保证1≤ n ≤2000,1≤ m ≤2000,1≤f≤n*m
分析
最先想到的就是暴搜,遍历每个位置,再从大到小遍历每个边长,统计0个数,满足<=f就输出枚举的边长。
但是显而易见复杂度已经上天了...
上千的数据量肯定TLE.....
降阶
二维的问题一般可以考虑下一维情况有没有较简单的算法?再推广到二维情况。
一维情况:
假设:有一个01字符串,要找出0个数不大于f的最长子串。怎么做呢?🤔
暴力:ans从大到小遍历,起始位置i,子串长度j,同时遍历,
导致复杂度过高的原因是:遍历过程中其实有“重复信息”被我们忽略了。如果用一次遍历,保存下尽可能多的信息,就可以避免再次遍历了。
前缀和算法:一遍遍历,用一个数组记录下这之前0的个数。当我们需要知道某一个子串中含0的个数,只需要算出即可。
这样,一维的情况我们就优化成了
推广
到二维的情况呢?
我们可以用类似一维中前缀和的思想,用一个O(N^2)的遍历统计不同位置与坐标为(1,1)位置围成的矩形中的0个数。
根据小学的面积公式,如图:
()
可以得出下面的关系:
这样就可以统计出0的个数了。
此时的复杂度为。
敲下代码...
#include <bits/stdc++.h>
using namespace std;
int mat[3000][3000];
int memo[3000][3000];
int main() {
int n, m, f;
cin >> n >> m >> f;
for (int i = 0; i < n; i++) {//初始化
for (int j = 0; j < m; j++) {
memo[i][j] = 0;
mat[i][j] = 0;
}
}
for (int i = 1; i < n + 1; i++) {//注意这里从i=1,j=1开始存储!留一行一列便于memo数组初始化
for (int j = 1; j < m + 1; j++) {
cin >> mat[i][j];
}
}
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
memo[i][j] = memo[i - 1][j] + memo[i][j - 1] - memo[i - 1][j - 1];
if (mat[i][j] == 0)
memo[i][j]++;
}
}
for (int ans = min(m, n); ans >= 0; ans--) {
bool flag = 0;
for (int i = 1; i < n + 1; i++) {
if (flag == 1)
break;
for (int j = 1; j < m + 1; j++) {
int x = i - ans + 1;
int y = j - ans + 1;
if (x < 1 || y < 1)//越界,不判断
continue;
if (memo[i][j] - memo[x][y] <= f) {
flag = 1;
break;
}
}
}
if (flag == 1) {//能找到这样的正方形,输出结果
cout << ans;
break;
}
}
}
过了7个...还是不行,继续优化
二分优化
for (int ans = min(m, n); ans >= 0; ans--) 这次遍历可以用二分啊!
要遍历正方形的边长是单调的,也满足二分的条件(若较大的边长符合,那比它小的肯定也符合)
改进代码....
#include <bits/stdc++.h>
using namespace std;
vector<vector<int>> mat(3000, vector<int>(3000, 0));
vector<vector<int>> memo(3000, vector<int>(3000, 0));
//用vector吧,不然二维数组传参太麻烦了
bool func(vector <vector<int>> &memo, int ans, int n, int m, int f) {//判断ans是否满足,传引用&memo,更快
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
int x = i - ans + 1;
int y = j - ans + 1;
if (x < 1 || y < 1)
continue;
if (memo[i][j] - memo[x - 1][j] - memo[i][y - 1] + memo[x - 1][y - 1] <= f)//注意条件
{
return true;
}
}
}
return false;
}
int main() {
int n, m, f;
cin >> n >> m >> f;
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
cin >> mat[i][j];
}
}
for (int i = 1; i < n + 1; i++) {
for (int j = 1; j < m + 1; j++) {
memo[i][j] = memo[i - 1][j] + memo[i][j - 1] - memo[i - 1][j - 1];
if (mat[i][j] == 0)
memo[i][j]++;
}
}
int left = 0, right = min(n, m);//二分,左右界
int max_side = 0;
while (left <= right) {
int mid = (left + right) / 2;
if (func(memo, mid, n, m, f) == 1) {
max_side = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
cout << max_side;
}
时间复杂度
空间复杂度