关于最大子矩阵问题 (悬线法 | 单调栈)

具体参看 浅谈用极大化思想解决最大子矩形问题 这篇论文


其实本来是想做单调栈的,但是碰巧看到了最大子矩阵问题,当然可以用单调栈做,但是我先学习了悬线法,单调栈就先留个坑之后补一下

稍微说一下我对悬线法的理解,悬线的定义是上端点覆盖了一个障碍点或达到整个矩形上端的除两端外都不包含障碍点的竖线,通俗来说就是顶端是障碍点或者顶端,不包含障碍点的竖线,首先任意一个最大子矩阵都至少含有一个悬线,而悬线往两遍扫必然包含最大子矩阵。那么目前的问题就是,怎么找到所有的悬线,由于悬线和底部的点一一对应(顶端为某一障碍点的悬线可以有多个),所以可以通过枚举底端来实现。

简单说一下步骤,这是预处理

for (int i = 1; i <= n; i++)
        for (int j = 2; j <= m; j++) 
            if (map[i][j] == 1 && map[i][j - 1] == 1)   //两个都不是障碍,就扩展一下
                l[i][j] = l[i][j - 1];
    for (int i = 1; i <= n; i++)
        for (int j = m - 1; j >= 1; j--)
            if (map[i][j] == 1 && map[i][j + 1] == 1)
                r[i][j] = r[i][j + 1];

核心步骤

for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            if (map[i][j] == 1 && map[i - 1][j] == 1) {
                r[i][j] = min(r[i][j], r[i - 1][j]);
                l[i][j] = max(l[i][j], l[i - 1][j]);
                up[i][j] = up[i - 1][j] + 1;
            }
            ans = max(ans, (r[i][j] - l[i][j] + 1) * up[i][j]);
        }

 

下面是题目

这是luogu4171玉蟾宫的代码,一个板子题

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 1010;
int map[N][N], l[N][N], r[N][N], up[N][N];
int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            char s[2];
            scanf("%s", s);
            if (s[0] == 'F')
                map[i][j] = 1, l[i][j] = r[i][j] = j;
            else
                map[i][j] = 0, l[i][j] = 1, r[i][j] = 0;  //注意一下这里,保证下面遇到这种情况是左右长度0
            up[i][j] = 1;
        }
    for (int i = 1; i <= n; i++)
        for (int j = 2; j <= m; j++) 
            if (map[i][j] == 1 && map[i][j - 1] == 1)
                l[i][j] = l[i][j - 1];
    for (int i = 1; i <= n; i++)
        for (int j = m - 1; j >= 1; j--)
            if (map[i][j] == 1 && map[i][j + 1] == 1)
                r[i][j] = r[i][j + 1];
    int ans = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++) {
            if (map[i][j] == 1 && map[i - 1][j] == 1) {
                r[i][j] = min(r[i][j], r[i - 1][j]);
                l[i][j] = max(l[i][j], l[i - 1][j]);
                up[i][j] = up[i - 1][j] + 1;
            }
            ans = max(ans, (r[i][j] - l[i][j] + 1) * up[i][j]);
        }
    printf("%d", ans * 3);
    return 0;
}

 好来补一下单调栈的做法,其实这个单调栈的做法真的太奇妙了,我理解了好久

首先先是思想,对于每一行的每一个元素,用数组保存能向上扩展的高度

比如说 

FFF

FRF   

那么对于第一行,数组就是{1,1,1},第二行的数组就是{2,1,2},所以我们把任务简化为对每一行找到所有区间最小值乘以区间长度中的最大值,也就是我们对每一个数字,向左向右拓展到极限,再用这个长度乘以这个数字,那么我们就可以用单调栈了,我开始的想法是跑两遍,从左往右和从右往左两遍,找最长能拓展到哪里,这个很简单,但是我发现了网上一种只需要扫一遍的代码,非常的神奇。

用a数组储存目前最远向左扩展到位置i的值,那么在对单调栈进行操作时,当对第i个进行操作时,可以知道往前扩展到的位置,那么这时候就把这个位置的值改成a[i],那么每次pop的过程,如果一个值要pop了,说明它向右到了极限,所以可以更新ans。

这是代码

#include <cstdio>
#include <stack>
#include <algorithm>
using namespace std;
const int N = 1010;
int h[N], a[N];
stack < int > st;
int main() {
    int n, m, ans = 0;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            char s[2];
            scanf("%s", s);
            if (s[0] == 'F')
                h[j] = h[j] + 1;
            else
                h[j] = 0;
            a[j] = h[j];
            int top = j;
            while (!st.empty() && a[st.top()] > a[j]) {
                top = st.top();
                st.pop();
                ans = max(ans, (j - top) * a[top]);
            }
            a[top] = a[j];   //这一步很关键也很神奇
            st.push(top);
        }
        while (!st.empty()) {
            ans = max(ans, (m + 1 - st.top()) * a[st.top()]);
            st.pop();
        }
    }
    printf("%d", ans * 3);
    return 0;
}

 

posted @ 2020-02-28 01:18  cminus  阅读(277)  评论(0编辑  收藏  举报