「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;
}

 

posted @ 2019-07-24 08:39  Rain_day  阅读(139)  评论(0编辑  收藏  举报