洛谷题单指南-常见优化技巧-P4147 玉蟾宫

原题链接:https://www.luogu.com.cn/problem/P4147

题意解读:找到一个只包含'F'的最大的子矩形。

解题思路:

方法1:设R为0,F为1,先计算二维前缀和,再枚举所有子矩形左上角(x1,y1)、右下角(x2,y2),计算子矩形的区间和,更新最大值,只能得到部分分。

方法2:

对于二维矩阵每个点,定义三个属性:h[][],l[][],r[][]

h表示该点最大悬线高度,所谓悬线,如图

点(2,3)的悬线是从(2,3)往上一共有多少个F,所有h[2][3] = 2, h[5][4] = 5

如何计算h[][]?

可以通过递推:

h[i][j] =h[i-1][j] +1; //每个点的悬线高度等于上一个点的悬线高度+1

知道了每个点往上最大的高度,还需要知道每个点这个高度能到左、右什么位置,所以l[][], r[][]就分别表示某个点的悬线能到的最左、最右的坐标。

有了这些属性,只需要枚举每一个点,计算h * (r - l + 1) * 3,即可得到最大的结果。

那么,问题就剩下如何计算l[][], r[][]?

对于l[][]

第一步:遍历每一行,每一行从左到右遍历,将l[i][j]设置为从(i,j)能到的最左边的F的纵坐标,可以通过递推得到

    for(int i = 1; i <= n; i++)
    {
        for(int j = 2; j <= m; j++)
        {
            if(a[i][j] == 'F' && a[i][j - 1] == 'F')
            {
                l[i][j] = l[i][j - 1]; //在每一行能到的最左边的F,如果相邻两个点都是F,则为左边点的l值
            }
        }
    }

第二步:遍历每一行,每一行从左到右遍历,将l[i][j]更新为(i,j)的悬线能到的最左边的纵坐标(所谓悬线能到,是指从(i,j)往左的悬线高度都不低于h[i][j]),同样可以通过递推得到

    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(a[i][j] == 'F' && a[i - 1][j] == 'F')
            {
                l[i][j] = max(l[i][j], l[i - 1][j]); //每个点的悬线能到的最左边,如果上一个点l值更大,则更新为上一个点的l
            }
        }
    }

对于r[][]

第一步:遍历每一行,每一行从右到左遍历,将l[i][j]设置为从(i,j)能到的最右边的F的纵坐标,可以通过递推得到

    for(int i = 1; i <= n; i++)
    {
        for(int j = m - 1; j >= 1; j--)
        {
            if(a[i][j] == 'F' && a[i][j + 1] == 'F')
            {
                r[i][j] = r[i][j + 1]; //在每一行能到的最右边的F,如果相邻两个点都是F,则为右边点的r值
            }
        }
    }

第二步:遍历每一行,每一行从左到右遍历,将r[i][j]更新为(i,j)的悬线能到的最右边的纵坐标(所谓悬线能到,是指从(i,j)往右的悬线高度都不低于h[i][j]),同样可以通过递推得到

    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(a[i][j] == 'F' && a[i - 1][j] == 'F')
            {
                r[i][j] = min(r[i][j], r[i - 1][j]); //每个点的悬线能到的最右边,如果上一个点r值更小,则更新为上一个点的r
            }
        }
    }

 100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 1005;
int n, m, ans;
char a[N][N];
int h[N][N]; //每个点是F且能向上达到最大高度(悬线高度)
int l[N][N]; //每个点的悬线能到达的最左端点
int r[N][N]; //每个点的悬线能达到的最右端点

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            cin >> a[i][j];
            if(a[i][j] == 'F') 
            {
                h[i][j] = h[i - 1][j] + 1; //每个点的悬线高度等于上一个点的悬线高度+1
                l[i][j] = r[i][j] = j; //初始化
            }
            else  h[i][j] = 0;
        }
    }

    for(int i = 1; i <= n; i++)
    {
        for(int j = 2; j <= m; j++)
        {
            if(a[i][j] == 'F' && a[i][j - 1] == 'F')
            {
                l[i][j] = l[i][j - 1]; //在每一行能到的最左边的F,如果相邻两个点都是F,则为左边点的l值
            }
        }
        for(int j = m - 1; j >= 1; j--)
        {
            if(a[i][j] == 'F' && a[i][j + 1] == 'F')
            {
                r[i][j] = r[i][j + 1]; //在每一行能到的最右边的F,如果相邻两个点都是F,则为右边点的r值
            }
        }
    }

    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= m; j++)
        {
            if(a[i][j] == 'F' && a[i - 1][j] == 'F')
            {
                l[i][j] = max(l[i][j], l[i - 1][j]); //每个点的悬线能到的最左边,如果上一个点l值更大,则更新为上一个点的l
                r[i][j] = min(r[i][j], r[i - 1][j]); //每个点的悬线能到的最右边,如果上一个点r值更小,则更新为上一个点的r
            }
            ans = max(ans,  3 * (r[i][j] - l[i][j] + 1) * h[i][j]);
        }
    }
    cout << ans;

    return 0;
}

 

posted @ 2024-08-15 16:32  五月江城  阅读(14)  评论(0编辑  收藏  举报