单调栈的使用(刷题)
P4147 玉蟾宫
题目背景
有一天,小猫 rainbow 和 freda 来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。
题目描述
这片土地被分成 \(N\times M\) 个格子,每个格子里写着 'R' 或者 'F',R 代表这块土地被赐予了 rainbow,F 代表这块土地被赐予了 freda。
现在 freda 要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着 'F' 并且面积最大。
但是 rainbow 和 freda 的 OI 水平都弱爆了,找不出这块土地,而蓝兔也想看 freda 卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为 \(S\),它们每人给你 \(S\) 两银子。
输入格式
第一行两个整数 \(N\),\(M\),表示矩形土地有 \(N\) 行 \(M\) 列。
接下来 \(N\) 行,每行 \(M\) 个用空格隔开的字符 'F' 或 'R',描述了矩形土地。
输出格式
输出一个整数,表示你能得到多少银子,即 (\(3\times \text{最大 'F' 矩形土地面积}\)) 的值。
输入输出样例 #1
输入 #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\%\) 的数据,\(1 \leq N, M \leq 200\)。
对于 \(100\%\) 的数据,\(1 \leq N, M \leq 1000\)。
题意分析
这个题目要我们找出最大的矩形面积。
我们发现这个矩形有很多种选取方式,采用贪心的思路肯定有问题,所以我们应该采用枚举的方法来找,逐个枚举,肯定不好,所以我们使用单调栈来优化。
我们重新规划一下图:对于每一位置,以mp[i][j]代表现在上方有mp[i][j]个直接相连的f
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (f[i][j] == 0) {
mp[i][j] = (i == 1) ? 1 : mp[i - 1][j] + 1;
} else {
mp[i][j] = 0;
}
}
}
然后我们对这个新图进行单调栈处理,保证栈顶为高度最小的列,那么我们栈内的所有元素都可以以这个高度划出矩形,并且我们储存每一个点的最远高度。然后进行单调栈处理,可以减少很多不必要的计算。
void work(i64 row)
{
stack<node> s;
// 在末尾添加一个高度为0的柱子,确保所有柱子都能被处理
for (int i = 1; i <= m + 1; i++)
{
i64 current_height = (i <= m) ? mp[row][i] : 0;
node current = {current_height, 0};
i64 accumulated_width = 0;
// 弹出栈中所有高度大于等于当前柱子的柱子
while (!s.empty() && s.top().height >= current.height)
{
accumulated_width += s.top().width;
// 计算以弹出柱子为高的矩形面积
ans = max(ans, s.top().height * accumulated_width);
s.pop();
}
// 将当前柱子压入栈中
current.width = accumulated_width + 1;
s.push(current);
}
}
所以我们通过这个单调栈就解决了这个问题
接下来我们再来看一道类似的题目:
P1950 长方形
题目描述
小明今天突发奇想,想从一张用过的纸中剪出一个长方形。
为了简化问题,小明做出如下规定:
(1)这张纸的长宽分别为 \(n,m\)。小明将这张纸看成是由\(n\times m\)个格子组成,在剪的时候,只能沿着格子的边缘剪。
(2)这张纸有些地方小明以前在上面画过,剪出来的长方形不能含有以前画过的地方。
(3)剪出来的长方形的大小没有限制。
小明看着这张纸,想了好多种剪的方法,可是到底有几种呢?小明数不过来,你能帮帮他吗?
输入格式
第一行两个正整数 \(n,m\),表示这张纸的长度和宽度。
接下来有 \(n\) 行,每行 \(m\) 个字符,每个字符为 * 或者 .。
字符 * 表示以前在这个格子上画过,字符 . 表示以前在这个格子上没画过。
输出格式
仅一个整数,表示方案数。
输入输出样例 #1
输入 #1
6 4
....
.***
.*..
.***
...*
.***
输出 #1
38
说明/提示
【数据规模】
对 \(10\%\) 的数据,满足 \(1\leq n\leq 10,1\leq m\leq 10\)
对 \(30\%\) 的数据,满足 \(1\leq n\leq 50,1\leq m\leq 50\)
对 \(100\%\) 的数据,满足 \(1\leq n\leq 1000,1\leq m\leq 1000\)
题意分析
这个题目要求我们找出所有的矩形的可能,我们依旧可以采用上面的方式,去优化图的存储,对于每一个点,
他找到左边第一个不大于他的(计数量为x),和右边第一个小于他的(计数量为y),然后这中间一共可以有xy(mp[i][j])个(这样是为了避免重复计算,表示包含这一个点的矩形),然后我们就可以计算出答案
void work(int x)
{
stack<int> s;
for(int i=1;i<=m;i++)
{
while(!s.empty()&&f[x][s.top()]>=f[x][i])
{
r[s.top()]=i;s.pop();
}
s.push(i);
}
while(!s.empty())
{
r[s.top()]=m+1;
s.pop();
}
for(int i=m;i>=1;i--)
{
while(!s.empty()&&f[x][s.top()]>f[x][i])
{
l[s.top()]=i;s.pop();
}
s.push(i);
}
while(!s.empty())
{
l[s.top()]=0;
s.pop();
}
}
for(int i=1;i<=n;i++)
{
work(i);
for(int j=1;j<=m;j++)
{
ans+=(j-l[j])*(r[j]-j)*f[i][j];
}
}
AC代码
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
using i64=long long;
i64 n,m;
i64 mp[1005][1005],f[1005][1005],l[1005],r[1005];
i64 ans=0;
void work(int x)
{
stack<int> s;
for(int i=1;i<=m;i++)
{
while(!s.empty()&&f[x][s.top()]>=f[x][i])
{
r[s.top()]=i;s.pop();
}
s.push(i);
}
while(!s.empty())
{
r[s.top()]=m+1;
s.pop();
}
for(int i=m;i>=1;i--)
{
while(!s.empty()&&f[x][s.top()]>f[x][i])
{
l[s.top()]=i;s.pop();
}
s.push(i);
}
while(!s.empty())
{
l[s.top()]=0;
s.pop();
}
}
int main() {
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char c;
cin>>c;
if(c=='.')mp[i][j]=1;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(mp[i][j]==1)
{
f[i][j]=f[i-1][j]+1;
}
else f[i][j]=0;
}
for(int i=1;i<=n;i++)
{
work(i);
for(int j=1;j<=m;j++)
{
ans+=(j-l[j])*(r[j]-j)*f[i][j];
}
}
cout<<ans;
return 0;
}

浙公网安备 33010602011771号