「ZJOI2007」棋盘制作 - 悬线法
在一个$N \times M$的$0 1$矩阵中,求面积最大的相邻位置数字不同的矩形和正方形。
题目链接:BZOJ1057
乍一看,也许暴力可以解决问题,可以暴力的枚举所取图形的长和宽,然后再暴力的枚举。
但是这样的时间复杂度高达$O(n^2 m^2)$,肯定行不通,而且很难写。
这时候,我们引入“悬线法”。
对于每个位置,我们预处理出此节点向上方最长能够延申的合法长度$left$。
再用另一个数组预处理出此节点向下方最长能够延申的合法长度$right$。
然后再横向处理矩阵,求出每个点的横向长度合法最长值,用一条线连结。
称为「悬线」。求出悬线向上向下能延伸到的最远位置,则悬线上下移动的轨迹为一个合法子矩阵,
且这个子矩阵的上边、左边、下边不能延伸。如果它的右边可以延伸,则在接下来的枚举中,延伸得到的矩阵可以被枚举到,否则它就是一个极大合法子矩阵。
时间复杂度$O(nm)$
代码如下
#include <stdio.h> #include <algorithm> int n, m; int qrt(int x) { return x * x; } int pe[2005][2005], b[2005][2005], c[2005][2005]; int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { scanf("%d", &pe[i][j]); } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (j == 1 || pe[i][j] == pe[i][j - 1]) b[i][j] = 1; else { b[i][j] = b[i][j - 1] + 1; } } for (int j = m; j >= 1; j--) { if (j == m || pe[i][j] == pe[i][j + 1]) c[i][j] = 1; else { c[i][j] = c[i][j + 1] + 1; } } } int sq_max = 0, rec_max = 0; for (int j = 1; j <= m; j++) { int up = 0, left = 0, right = 0; for (int i = 1; i <= n; i++) { if (i == 1 || pe[i][j] == pe[i - 1][j]) { up = 1; left = b[i][j]; right = c[i][j]; } else { up++; left = std::min(left, b[i][j]); right = std::min(right, c[i][j]); } rec_max = std::max(rec_max, up * (left + right - 1)); sq_max = std::max(sq_max, qrt(std::min(left + right - 1, up))); } } printf("%d\n%d\n", sq_max, rec_max); return 0; }
人十我百 人百我万