2019牛客暑期多校训练营 (第八场) A All-one Matrices【降维+单调栈+前缀和】

题目描述

Gromah and LZR entered the great tomb, the first thing they see is a matrix of size n×m, and the elements in the matrix are all 0 or 1.

LZR finds a note board saying "An all-one matrix is defined as the matrix whose elements are all 1​, you should determine the number of all-one submatrices of the given matrix that are not completely included by any other all-one submatrices".

Meanwhile, Gromah also finds a password lock, obviously the password should be the number mentioned in the note board!

Please help them determine the password and enter the next level.

输入描述

The first line contains two positive integers n,m​, denoting the size of given matrix.

Following n lines each contains a string with length m​, whose elements are all 0​ or 1​, denoting the given matrix.

1\leq n,m\leq 3000

输出描述

Print a non-negative integer, denoting the answer.

示例输入 

3 4
0111
1110
0101

示例输出 

5

说明 

The 5 matrices are (1,2)−(1,4),  (1,2)−(2,3),  (1,2)−(3,2),  (2,1)−(2,3),  (3,4)−(3,4)​.

题目大意:

有一个n*m的01矩阵,要求找出有多少个最大的全1子矩阵(即该子矩阵的所有元素都为1,且不存在另一个全1子矩阵能够完全包含这个矩阵)。

分析:

首先要进行降维处理,对于每一行,记录从每个位置向上连续的1的个数,即将子矩阵尽可能地向上拓展。

然后,依次处理每一行, 先从左至右利用单调上升的栈找到每个位置向左拓展的最远位置,存储在L[ j ]中,再从右至左利用单调栈找到每个位置向右拓展的最远位置,存储在R[ j ]中。

至此已经将子矩阵向上、向左、向右拓展到了最远的位置,接下来要考虑向下的最远拓展。可以记录每一行的前缀和,那么对于每个L[ j ]和R[ j ]之间的矩阵,可以在 O(1) 的时间内找到下一层L[ j ]和R[ j ]之间1的个数 ,从而可以决定是否能向下拓展。

最后还有一个问题,在遍历每一行的时候,不同位置的L[ j ]和R[ j ]可能代表了同一个矩阵,所以还要注意要去重。

具体解释见代码。

#include<bits/stdc++.h>

using namespace std;

const int maxn = 3e3+5;

int n,m,h[maxn][maxn],sum[maxn][maxn],q[maxn],R[maxn],L[maxn];

char s[maxn];

int main() {

    scanf("%d%d", &n, &m);
    
    memset(h,0,sizeof(h));

    for (int i = 1; i <= n; i++) {

        scanf("%s", s + 1);

        for (int j = 1; j <= m; j++) {

            if (s[j] == '1')

                h[i][j] = h[i - 1][j] + 1;

            sum[i][j] = sum[i][j - 1] + s[j] - '0';

        }

    }

    int ans = 0;
    
	//处理每一行 
    for (int i = n; i; i--) {

        stack<int> sta;

        sta.push(m + 1);

        //从右往左找出每个点j以h[i][j](向上连续1的高度)为高度的矩形右边界R[j]

        for (int j = m; j; j--) {

            while (!sta.empty() && h[i][j] <= h[i][sta.top()])

                sta.pop();

            if (sta.empty()) R[j] = m;//栈空,说明是当前最小的,则右边界为 m 

            else R[j] = sta.top() - 1;

            sta.push(j);

        }

        while (!sta.empty()) sta.pop();

        sta.push(0);

        //从左往右找出每个点j以h[i][j](向上连续1的高度)为高度的矩形左边界L[j]

        for (int j = 1; j <= m; j++) {

            while (!sta.empty() && h[i][j] <= h[i][sta.top()]) sta.pop();

            if (sta.empty()) L[j] = 1;//栈空,说明是当前最小的,则左边界为 1 

            else L[j] = sta.top() + 1;

            sta.push(j);

        }
        
        map<pair<int,pair<int,int>>,int> mp;//利用map去重,将每个矩阵包装为3元组(h[i][j],(L[j],R[j])) 

        for (int j = 1; j <= m; j++) {
        
        	int l=L[j];
        	
        	int r=R[j];
        	
        	if(h[i][j]==0)  continue;//h[i][j]=0,不可能形成矩阵 
        	
        	if(mp[make_pair(h[i][j],make_pair(L[j],R[j]))]==1)  continue;//这个子矩阵已经处理过 
        	
        	else{
        		if (sum[i + 1][r] - sum[i + 1][l - 1] != r - l + 1){//查看是否可以向下拓展,不能拓展则答案加一 
        			
        			ans++;
        			
        			mp[make_pair(h[i][j],make_pair(L[j],R[j]))]=1;
        			
//        			cout<<h[i][j]<<" "<<L[j]<<" "<<R[j]<<endl;
				}
			}

        }

    }

    printf("%d\n", ans);

    return 0;

}
发布了30 篇原创文章 · 获赞 5 · 访问量 900

猜你喜欢

转载自blog.csdn.net/qq_42840665/article/details/99202355