Luogu4147 玉蟾宫(单调栈求解最大子矩阵)

本文解析了编程题P4147《玉蟾宫》的解法,介绍了如何使用单调栈求解最大子矩阵问题,涉及动态规划和数据结构,适合OI水平提升。通过实例和代码展示了如何在F土地上找到最大面积的'F'矩形并计算奖励。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

玉蟾宫

题目背景

有一天,小猫 rainbow 和 freda 来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。

题目描述

这片土地被分成 N × M N\times M N×M 个格子,每个格子里写着 ‘R’ 或者 ‘F’,R 代表这块土地被赐予了 rainbow,F 代表这块土地被赐予了 freda。

现在 freda 要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着 ‘F’ 并且面积最大。

但是 rainbow 和 freda 的 OI 水平都弱爆了,找不出这块土地,而蓝兔也想看 freda 卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为 S S S,它们每人给你 S S S 两银子。

输入格式

第一行两个整数 N N N M M M,表示矩形土地有 N N N M M M 列。

接下来 N N N 行,每行 M M M 个用空格隔开的字符 ‘F’ 或 ‘R’,描述了矩形土地。

输出格式

输出一个整数,表示你能得到多少银子,即 ( 3 × 最大 ’F’ 矩形土地面积 3\times \text{最大 'F' 矩形土地面积} 3×最大 ’F’ 矩形土地面积) 的值。

输入输出样例

输入 #1
5 6
R F F F F F
F F F F F F
R R R F F F
F F F F F F
F F F F F F
输出 #1
45

说明/提示

对于 50 % 50\% 50% 的数据, 1 ≤ N , M ≤ 200 1 \leq N, M \leq 200 1N,M200
对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 1000 1 \leq N, M \leq 1000 1N,M1000

题解

最大子矩阵居然真的可以用数据结构,是👴浅薄了。

这钵是用单调栈求解最大子矩阵问题,首先我们可以预处理出每个F点能向上延伸的长度h[i][j],如此就把整个网格分隔成了若干宽度为 1 1 1的矩形,以样例为例:
在这里插入图片描述
可以发现,对于并列的若干矩形,想要把它们合并成一个大矩形,那么矩形的高度只取决于最矮的那个矩形,所以当我们从左向右遍历所有矩形时,对于新加入的矩形,前面已加入的比它高的矩形都无意义,因为要合并的话新矩形的高就等于它自己的高,我们大可弹出前面的矩形,只保留宽度信息。

所以我们可以维护一个单增的单调栈,栈内保存的是当前加入矩形的高度和加入时弹出的若干矩形的宽度之和+1(1是自己的宽度)。在某矩形弹出栈的时候用(该矩形记录的加入该矩形时弹出矩形的累计宽度+此次弹出时在该矩形之前弹出的矩形的累计宽度) × \times ×高度来更新答案。因为该矩形自己记录的只包含以比它高且在它左侧的矩形合并出的矩形,而在单增单调栈中该矩形右侧后面加入的矩形实际上也比它高,要合并的话也是最终矩形也是它的高度,所以我们就再加上在它之前弹出的矩形的累计宽度。另外,最后记得用栈内的元素再更新一次答案。

但是这就存在一个问题,同一列可能有多个矩形,而上述做法只能解决一列只有一个矩形的问题。于是我们直接暴力枚举行,每一行都做一次单调栈,复杂度 O ( n 2 ) O(n^2) O(n2)

上述描述可能有点抽象,建议结合代码理解。

代码

scanf用多了,今天才知道cin读字符可以过滤空格,女少口阿。

#include<bits/stdc++.h>
using namespace std;
const int M=1005;
struct Rec{int h,w;}stk[M];
int n,m,h[M][M],ans,top;
void ask(int x)
{
    stk[top=1]=(Rec){h[x][1],1};
    for(int i=2,w;i<=m;++i)
    {
        for(w=0;top&&h[x][i]<=stk[top].h;--top)w+=stk[top].w,ans=max(ans,w*stk[top].h);
        stk[++top]=(Rec){h[x][i],w+1};
    }
    for(int w=0;top;--top)
    {
        w+=stk[top].w;
        ans=max(ans,w*stk[top].h);
    }
}
void in()
{
    scanf("%d%d",&n,&m);
    char a;
    for(int i=1;i<=n;++i)for(int j=1;j<=m;++j)
    {
        cin>>a;
        h[i][j]=a=='F'?h[i-1][j]+1:0;
    }
}
void ac()
{
    for(int i=1;i<=n;++i)ask(i);
    printf("%d\n",ans*3);
}
int main()
{
    in(),ac();
    system("pause");
}
### 使用单调栈求解最大子矩阵问题 #### 算法概述 对于给定的一个由 `0` `1` 组成的二维矩阵,目标是找到其中仅包含 `1` 的最大矩形区域并返回其面积。此问题可以通过将二维问题转化为多个一维问题来解决,具体方法如下: 通过逐行遍历矩阵,并利用前缀的思想计算每列的高度(即连续 `1` 的数量),从而构建一个高度数组 `heights[]`。随后,针对每一行的高度数组应用 **单调栈** 来找出当前行所能形成的最大矩形面积。 最终的结果是从所有行中得到的最大矩形面积中的最大值[^3]。 --- #### 关键步骤解析 ##### 1. 高度数组的构建 定义一个长度等于矩阵宽度的数组 `heights[]`,初始化为零。每次迭代一行时更新该数组,使得 `heights[j]` 表示第 `j` 列从当前位置向上数连续 `1` 的个数。如果遇到 `0`,则重置对应列的高度为零。 ```python def build_heights(matrix, heights): rows, cols = len(matrix), len(matrix[0]) for j in range(cols): # 初始化第一行的高度 heights[j] = int(matrix[0][j]) for i in range(1, rows): # 更新后续行的高度 for j in range(cols): if matrix[i][j] == '1': heights[j] += 1 else: heights[j] = 0 ``` 此处的时间复杂度为 O(m * n),其中 m 是矩阵的行数,n 是列数[^4]。 --- ##### 2. 单调栈的应用 为了高效地求解单行内的最大矩形面积,采用单调栈的方法。核心思路是对每个柱状体(代表某一列的高度)寻找左侧第一个小于它的索引 `left_idx` 右侧第一个小于它的索引 `right_idx`,进而计算以其为高的矩形面积 `(right_idx - left_idx - 1) * height[idx]`。 以下是基于单调栈的具体实现逻辑: - 构建一个严格递增的栈; - 当发现新的高度低于栈顶对应的柱高时,弹出栈顶元素并计算以该柱高为基础的矩形面积; - 将新高度压入栈中继续处理。 ```python def max_rectangle_in_histogram(heights): stack = [] max_area = 0 heights.append(0) # 添加哨兵简化边界条件 for idx, h in enumerate(heights): while stack and heights[stack[-1]] >= h: current_height = heights[stack.pop()] width = idx if not stack else (idx - stack[-1] - 1) max_area = max(max_area, current_height * width) stack.append(idx) heights.pop() # 移除哨兵恢复原数组状态 return max_area ``` 上述函数实现了对任意输入的一维直方图求解最大矩形面积的功能,时间复杂度为 O(n)[^5]。 --- ##### 3. 整合过程 最后一步是将以上两部分结合起来完成整个算法流程。依次遍历矩阵的每一行,动态维护高度数组并通过单调栈获取局部最优解;全局最优解则是这些局部结果中的最大者。 完整代码展示如下: ```python def maximalRectangle(matrix): if not matrix or not matrix[0]: return 0 rows, cols = len(matrix), len(matrix[0]) heights = [0] * cols max_area = 0 def calculate_max_area_with_stack(): nonlocal heights, max_area stack = [] heights.append(0) # 哨兵 for idx, h in enumerate(heights): while stack and heights[stack[-1]] >= h: current_height = heights[stack.pop()] width = idx if not stack else (idx - stack[-1] - 1) max_area = max(max_area, current_height * width) stack.append(idx) heights.pop() # 清理哨兵 for row in matrix: for j in range(cols): heights[j] = heights[j] + 1 if row[j] == '1' else 0 calculate_max_area_with_stack() return max_area ``` 整体时间复杂度为 O(m * n),空间复杂度主要取决于辅助使用的栈结构以及存储高度信息的数据结构,均为线性级别。 --- ### 结果验证 考虑样例输入: ``` [ ["1","0","1","0","0"], ["1","0","1","1","1"], ["1","1","1","1","1"], ["1","0","0","1","0"] ] ``` 执行上述程序可得出正确答案 `6`,与预期一致[^2]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值