题目链接

单调栈求极大子矩阵

题意:极大子矩阵的个数

单调栈求子矩阵的个数就不说明了,这里有两点需要注意的。

  1. 因为求的是极大子矩阵,单调栈只能固定一个底边,求出以这个底边为基础的子矩阵个数,所以我们需要确认的是当前求出来的子矩阵还能不能向下扩展,如果不能才能计数。
    解决的方法:假设这个矩阵的左右边界为L和R,那么对于下一行的L和R这个区间范围内是不是全都是1,如果不是的话,说明当前矩阵不能在向下扩展了。先预处理出每一行的前缀和,就能通过差分O(1)判断
  2. 当前的矩阵的左端和右端要确定好。首先是进栈的情况:只要栈顶比当前矩阵高,就弹出栈,弹出去的矩阵的左端也是当前矩阵的左端。
    然后是出栈的情况:在没有任何一个矩阵出栈前,栈内的矩阵都是单调递增的,我们定最高的那个矩阵的右端为R,对于栈内所有矩阵的右端都是R。

要想清楚左端和右端的情况。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int INF = 0x3f3f3f3f;
const int N = 3000+10;

int dp[N][N], c[N][N];
stack <pair<int, int> > sk;

int main(void)
{
	//ios::sync_with_stdio(false);
	//cin.tie(false); cout.tie(false);
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= m; ++j) scanf("%1d", &dp[i][j]);
    memcpy(c, dp, sizeof(dp));
    for(int i = 2; i <= n; ++i)
        for(int j = 1; j <= m; ++j) if(dp[i][j]) dp[i][j] += dp[i-1][j];

    for(int i = 1; i <= n; ++i)
        for(int j = 2; j <= m; ++j) c[i][j] += c[i][j-1];

    int ans = 0;
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m+1; ++j) {
            int pos = j;
            while(!sk.empty() && dp[i][sk.top().first] > dp[i][j]) {
                pair<int, int> t = sk.top(); sk.pop();
                pos = t.second;
                if(i+1 <= n && c[i+1][j-1]-c[i+1][t.second-1] != j-1-t.second+1) ans++;
                else if(i == n) ans++;
            }
            if(!sk.empty() && dp[i][sk.top().first] == dp[i][j]) sk.top().first = j;
            else if(dp[i][j]) sk.push(make_pair(j, pos));
        }
    }
    printf("%d\n", ans);
	return 0;
}