古典叙事 · 技术札记

序章

山河有卷
人间有声

写代码,也写长风、旧城与未熄的灯。
愿每一篇随笔,都有自己的山水与回声。
阅览随笔

单调栈的使用(刷题)

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;
}
posted @ 2025-09-17 11:19  Morphis‘  阅读(19)  评论(0)    收藏  举报

特别策划 · CINEMATIC NOTES

风沙与孤骑

风起塞外,
胜负在刀剑之前。

“把复杂拆成秩序,把未知写成答案。”
Morphis · 山河一卷 愿你从这里出发,仍能听见山风。 影像:farfarSébastien Goldberg / Unsplash