洛谷 P1950 长方形(单调栈)

传送门


题目大意

给出一个 n ∗ m ( 1 ≤ n , m ≤ 1000 ) n*m(1 \leq n, m \leq 1000) nm(1n,m1000) 的矩阵,矩阵元素可以为 ‘.’, ‘*’。求出所有的仅有 . . . 构成的矩形的个数。

解题思路

这题应该由两个部分构成,一是如何统计矩形,二是如何不重不漏且在时间复杂度内统计矩形。

统计矩形

若没有 ‘*’ 的限制在矩阵内找有多少个矩形,实际上就是这道题的一个简化版:P2241 统计方形

考虑矩形的性质,可以由如下角度入手去统计:

  • 固定矩形的左上角和右下角,可以唯一确定一个矩形。
  • 固定矩阵的一条底边,然后根据高度可以唯一地确定一个矩形。

性质一显然超时,考虑性质二,可以得出一个规律,在 n ∗ m n*m nm 的矩阵中有 n ( n + 1 ) 2 ∗ m ( m + 1 ) 2 \frac{n(n + 1)}{2} * \frac{m(m + 1)}{2} 2n(n+1)2m(m+1) 个矩形。

解决本题

假设 d [ i ] [ j ] d[i][j] d[i][j] 代表位置 ( i , j ) (i, j) (i,j) 能向上扩展的最长连续 ‘.’ 的长度,实际上也就是上方第一个 ‘*’ 的下一个的位置, d d d 的初始化很好写。

考虑单调栈的经典问题,找水平线上的最大矩形,考虑每一行,把 d d d 当做竖直的宽为一的矩形:每一个位置 j j j 作为底边,求出左边比它矮的第一个矩形的位置 L L L 和右边比它矮的第一个矩形 R R R ,就是说包含当前点的所有区间中,左边可选 j − L j - L jL 种,右边可选 R − j R - j Rj 种,那么当前位置应该累加的答案数目就是 ( j − L ) ∗ ( R − j ) ∗ d [ i ] [ j ] (j - L) * (R - j) * d[i][j] (jL)(Rj)d[i][j]

但是上述统计方法会重复,如果有连续的一段全为同一个数,这样是重复的,例如 1    1    1    1 1~~1~~1~~1 1  1  1  1。说对于一段区间 [ i , j ] [i, j] [i,j] ,如何选出所有的子区间且不重复?从左到右固定每个 l ( i ≤ l ≤ j ) l(i \leq l \leq j) l(ilj) ,枚举 r ( l ≤ r ≤ j ) r(l \leq r \leq j) r(lrj) 即可。参考这个思路,我们求出每个位置左边不大于它的第一个数的位置,右边小于它的第一个数的位置,即可做到不重复。

因为我们是按行进行的,且利用了上述统计矩形的性质二,因此这种统计方法不会漏

#include <bits/stdc++.h>

using namespace std;
#define ENDL "\n"
typedef long long ll;
typedef pair<int, int> pii;
const double eps = 1e-6;
const int Mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 10;

int d[1005][1005];
int L[1005], R[1005];
int st[1005];

int main() {
    // freopen("in.txt", "r", stdin);
    // freopen("out.txt", "w", stdout);
    ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n, m;
    char ch;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> ch;
            if (ch == '.') d[i][j] = d[i - 1][j] + 1;
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++) {
        int top = 0;
        d[i][0] = d[i][m + 1] = -inf;
        st[++top] = 0;
        for (int j = 1; j <= m; j++) {
            while (d[i][st[top]] > d[i][j]) top--;
            L[j] = st[top];
            st[++top] = j;
        }
        top = 0;
        st[++top] = m + 1;
        for (int j = m; j >= 1; j--) {
            while (d[i][st[top]] >= d[i][j]) top--;
            R[j] = st[top];
            st[++top] = j;
        }
        for (int j = 1; j <= m; j++) ans += (R[j] - j) * (j - L[j]) * d[i][j];
    }
    cout << ans << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值