P1950 长方形_NOI导刊2009提高(2)[单调栈][贡献法]

P1950 长方形_NOI导刊2009提高(2)

题意:给你\(n\times m\)的矩形,求没有*的子矩形数量。\(1\leq n,m\leq 1000\)

数据比较弱的题目是luoguP1191,\(1\leq n,m \leq 100\)。甚至可以用\(O(n^4)\)水过。

这道题的最优解法\(O(n^2)\)是这样的:

  • 枚举子矩形的底边,预处理出每一列可以往上延伸的长度\(h[i]\)
  • 跑两次单调栈,第一次找到每个数右边第一个小于它的数\(l[j]\),第二次找到每个数左边第一个小于等于它的数\(r[j]\)
  • 枚举每一列,那么这个点对答案的贡献就是\((j - l[j] + 1) \times (r[j] - j + 1) \times h[i]\)

其实枚举的\(j\)就是看看如果这个点是整个大矩形的短板,能够为答案贡献多少个矩形。高度就是从\(1\)\(h[i]\),共\(h[i]\)个。

两次单调栈要有一次小于等于!同时有多个短板的话可能会算重,我们这样算就不重不漏了。

有一个小细节容易错:在单调栈统计的时候,记录的答案不是那个\(i\),而是相应的\(i-1\)\(i+1\)。手摸一下就知道了。

代码:

#include<bits/stdc++.h>

const int maxn = 1005;
char ch[maxn][maxn];
int h[maxn];
int g1[maxn], g2[maxn];
int n, m;
long long ans;
void init() {
    // print
    //for(int i = 1; i <= m; i++) printf("%d ", h[i]);
    //printf("\n");
    memset(g1, 0, sizeof g1);
    memset(g2, 0, sizeof g2);
    std::stack<int> sta;// 找到第一个小于我的
    for(int i = 1; i <= m; i++) {
        while(!sta.empty() && h[sta.top()] > h[i]) {
            g2[sta.top()] = i - 1; sta.pop();// 是前面的那个
        }
        sta.push(i);
    }
    while(!sta.empty()) {
        g2[sta.top()] = m; sta.pop();
    }
    // print
    //for(int i = 1; i <= m; i++) printf("%d ", g2[i]);
    //printf("\n");
    for(int i = m; i >= 1; i--) {
        while(!sta.empty() && h[sta.top()] >= h[i]) {
            g1[sta.top()] = i + 1; sta.pop();// 是前面的那个
        }
        sta.push(i);
    }
    while(!sta.empty()) {
        g1[sta.top()] = 1; sta.pop();
    }
    // print
    //for(int i = 1; i <= m; i++) printf("%d ", g1[i]);
    //printf("\n");
}
int main() {
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%s", ch[i] + 1);
        for(int j = 1; j <= m; j++) {
            if(ch[i][j] == '*') h[j] = 0;
            else h[j]++;
        }
        init();
        for(int j = 1; j <= m; j++) {
            int temp = (j - g1[j] + 1) * (g2[j] - j + 1) * h[j];
            // print
            //printf("%d ", temp);
            ans += temp;
        }
        //printf("\n");
    }
    printf("%lld\n", ans);
    return 0;
}

转载于:https://www.cnblogs.com/Garen-Wang/p/10402070.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值