题目大意
给出一个 (n imes m (1 leq n, m leq 2500)) 的 (01) 矩阵,让你在其中找到一个最大的子矩阵使得该子矩阵除了一条对角线上的数字均为 (1) 之外,其他数字均为 (0) 。
思路
Level 1
暴力枚举每一个子矩阵,然后判断该矩阵是否为正方形且满足要求。
时间复杂度十分可观地达到了 $ O(n^3 m^3) ≈ O(n^6)$
Level 1.5
可以在 Level 1 的基础上,在枚举的时候就保证构造了正方形,时间复杂度 $ O(nm imes min(n, m)^3) ≈ O(n^5)$
Level 2
可以用 $ DP $ ,这里给出正方形的 (1) 对角线是从左下角到右上角的情况(例如:)
如果能够继承右上角的方格,那么 (f[i][j] = f[i - 1][j + 1] + 1)
否则,可以 (O(n)) 枚举一下可以继承多少。
最坏时间复杂度 (O(n^3)),调的好可以过此题。
Level 2.5
可以用前缀和优化,在判断能够继承多少的时候用二分,最坏时间复杂度 (O(n^2 log_{{ }_2}n)),基本上可以过此题。
Level 3
<-- 正解警告 -->
预处理出每个格子最多可以向左,向上,向右延伸多少个格子,使这些格子中的数都是 (0) (不包括这个格子)
然后对于每个格子,尝试以这个格子为左下角(右下角)建立一个子矩阵,不行就继承上一次 (DP) 的结果。
(f[i][j]) 表示以 (i, j) 为左下角(右下角)建立的子矩阵的大小。
(在实际实现的时候,两次 (DP) 的结果被我放在了同一个数组里面)
Code
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#define Max_N 103
using namespace std;
template <typename Int>
Int read() {
char ch = getchar();
while (!isdigit(ch)) ch = getchar();
Int a = (ch & 15);
ch = getchar();
while (isdigit(ch)) {
a = (a + (a << 2) << 1) + (ch & 15);
ch = getchar();
}
return a;
}
int n, m, a[2503][2503];
int lef[2503][2503] = {0}, upp[2503][2503] = {0}, rig[2503][2503] = {0};
int f[2503][2503] = {0};
int main()
{
n = read<int>();
m = read<int>();
for (register int i = 1; i <= n; i++) {
for (register int j = 1; j <= m; j++) {
a[i][j] = read<int>();
if (!a[i][j]) {
lef[i][j] = lef[i][j - 1] + 1;
upp[i][j] = upp[i - 1][j] + 1;
}
}
}
for (register int i = 1; i <= n; i++) {
for (register int j = m; j >= 1; j--) {
if (!a[i][j]) {
rig[i][j] = rig[i][j + 1] + 1;
}
}
}
register int ans = 0;
for (register int i = 1; i <= n; i++) {
for (register int j = 1; j <= m; j++) if (a[i][j]) {
f[i][j] = min(min(lef[i][j - 1], upp[i - 1][j]),
f[i - 1][j - 1]) + 1;
if (f[i][j] > ans) ans = f[i][j];
}
}
memset(f, 0, sizeof(f));
for (register int i = 1; i <= n; i++) {
for (register int j = m; j >= 1; j--) if (a[i][j]) {
f[i][j] = min(min(rig[i][j + 1], upp[i - 1][j]),
f[i - 1][j + 1]) + 1;
if (f[i][j] > ans) ans = f[i][j];
}
}
printf("%d", ans);
return 0;
}
最后,祝你们好运!