题解:洛谷 B4273 最大的矩形纸片
【题目来源】
洛谷:B4273 [蓝桥杯青少年组省赛 2023] 最大的矩形纸片
【题目描述】
一张半边参差不齐的网格纸(网格边长均为 \(1\)),有一边是完整没有破损的。现要从中剪出一片面积最大的矩形纸片。
给定网格纸中完整边的长度 \(N\)(\(1 \leq N \leq 1\,000\,000\)),以及网格中每一列残存部分的高度(\(1 \leq\) 高度 \(\leq 10\,000\)),输出能够剪出的最大矩形纸片面积。
【输入】
第一行输入一个正整数 \(N\)(\(1 \leq N \leq 1\,000\,000\)),表示纸片完整边的长度。
第二行输入 \(N\) 个正整数(\(1 \leq\) 正整数 \(\leq 10\,000\)),表示每列格子残存部分的高度,两个正整数之间用一个空格隔开。
【输出】
输出一个正整数,表示能够剪出的最大矩形纸片面积。
【输入样例】
6
3 2 1 4 5 2
【输出样例】
8
【核心思想】
-
问题分析:给定一个由 \(N\) 列残存高度构成的直方图,要求在其中找到一个面积最大的矩形,该矩形需完全位于各列高度的区域内。这与经典的“直方图中最大矩形”问题完全一致,本质是利用单调栈为每个高度找到其作为矩形高度时,能向左右扩展的最大宽度。
-
算法选择:
- 单调栈:本题 \(N\) 可达 \(10^6\),需 \(O(N)\) 算法。使用单调栈可以高效地为每一列 \(i\) 找到其左右两边第一个严格小于该列高度 \(a_i\) 的位置,从而确定以 \(a_i\) 为高的最大宽度。
- 具体策略:
- 求左边界:从左到右扫描,维护栈内下标对应高度单调递增(用
>=弹出,确保栈顶是左边最近高度小于当前高度的列)。左边界即为栈顶下标 +1(若栈为空则为 1)。 - 求右边界:从右到左扫描,同理维护单调递增栈(用
>=弹出),右边界即为栈顶下标 -1(若栈为空则为 \(N\))。 - 计算面积:枚举每一列 \(i\),面积 = \(a_i \times (r_i - l_i + 1)\),取最大值。
- 求左边界:从左到右扫描,维护栈内下标对应高度单调递增(用
-
关键步骤:
- 读入与预处理:读取 \(N\) 和 \(N\) 个高度,存入数组
a[1..N]。 - 单调栈求左边界
l[i]:- 从左向右遍历,当栈不空且栈顶高度
>= a[i]时,弹出栈顶。 - 若栈空,
l[i] = 1;否则l[i] = stk[tt] + 1。 - 将
i入栈。
- 从左向右遍历,当栈不空且栈顶高度
- 单调栈求右边界
r[i]:- 清空栈,从右向左遍历。
- 当栈不空且栈顶高度
>= a[i]时,弹出。 - 若栈空,
r[i] = n;否则r[i] = stk[tt] - 1。 - 将
i入栈。
- 枚举求最大值:遍历 \(i\),更新
ans = max(ans, a[i] * (r[i] - l[i] + 1))。 - 输出答案:
ans。
- 读入与预处理:读取 \(N\) 和 \(N\) 个高度,存入数组
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),每个元素最多入栈、出栈一次。
- 空间复杂度:\(O(N)\),用于存储高度和栈。
-
单调栈的核心思想:
- 扩展逻辑:对于第 \(i\) 列,以它作为矩形高度时,能向左右扩展到第一个高度小于它的列之前,这是保证矩形内部所有列高度均 \(\ge a_i\) 的必要且充分条件。
- 单调递增栈的作用:栈中维护的是高度递增的列下标序列,弹出所有高度 \(\ge\) 当前元素的元素后,栈顶即为左边最近的高度小于当前高度的列,从而快速确定左边界。右边界同理从右往左扫描。
- 适用场景:单调栈是解决“直方图最大矩形”以及所有需要寻找数组中每个元素左右两侧第一个比它小(或大)元素位置问题的标准技巧。
【算法标签】
普及+ #单调栈
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005;
int a[N]; // a[i] 表示第 i 列残存部分的高度
int stk[N]; // 单调栈,存储列的下标
int tt; // 栈顶指针
int n, ans;
int l[N], r[N]; // l[i] 表示以第 i 列为高的矩形能向左扩展的最远列下标
// r[i] 表示以第 i 列为高的矩形能向右扩展的最远列下标
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
// 单调栈求左边界 l[i]
// 维护栈内下标对应的高度单调递增(严格来说是非递减,此处用 >= 弹出)
for (int i = 1; i <= n; i++)
{
// 当栈不空且栈顶高度 >= 当前高度时,栈顶元素无法作为左边界,弹出
while (tt && a[stk[tt]] >= a[i])
tt--;
// 如果栈空,说明当前列左边没有比它矮的,左边界为 1
// 否则左边界为栈顶下标 + 1(栈顶是左边最近的高度小于当前高度的列)
l[i] = (tt == 0) ? 1 : stk[tt] + 1;
// 当前列入栈
stk[++tt] = i;
}
// 清空栈,准备求右边界
tt = 0;
// 单调栈求右边界 r[i]
// 从右向左扫描,维护栈内下标对应的高度单调递增
for (int i = n; i >= 1; i--)
{
// 当栈不空且栈顶高度 >= 当前高度时,栈顶元素无法作为右边界,弹出
while (tt && a[stk[tt]] >= a[i])
tt--;
// 如果栈空,说明当前列右边没有比它矮的,右边界为 n
// 否则右边界为栈顶下标 - 1(栈顶是右边最近的高度小于当前高度的列)
r[i] = (tt == 0) ? n : stk[tt] - 1;
// 当前列入栈
stk[++tt] = i;
}
// 枚举每一列作为矩形高度,计算最大面积
for (int i = 1; i <= n; i++)
ans = max(ans, a[i] * (r[i] - l[i] + 1));
cout << ans << endl;
return 0;
}
【运行结果】
6
3 2 1 4 5 2
8
浙公网安备 33010602011771号