最大全1子矩阵 单调栈

原题POJ 3494

题意

给出一个矩阵,求全1子矩阵的最大面积

解析

开局的处理方式和最大求和子矩阵类似,压缩处理。

预处理h[i][j],表示第i行第j列往上(行数递减方向)可以接上的全1串的最长长度,然后处理第一行到第i行的ans时,就可以看成处理h[i]一行了

eg:

n=3 m=4

 M数组            H数组
0 1 1 0         0 1 1 0
1 1 1 1   -->   1 2 2 1 
1 0 1 0         2 0 3 0 

接下来,对于每一行该怎么处理?

最大面积一定是某一个点的高 * 往左右延大于等于这个高的长度,所以只要对于每个高,处理出其可以延伸的左端点和右端点,ans=max(ans,H*len),用单调栈可以在n的时间内得到所有高的len

单调栈

假设H数组如下

这里写图片描述

维护一个单调不减的栈,首先s[0]=0,h[1]=1>0,入栈s[1]=1(下标),h[2]>s[1],入栈s[2]=2,同理s[3]=3

这个时候,每个入栈的都比前面的大,即当前为最大,故当前高度的左端点就是前面那个的下标,有 L[1]=0,L[2]=1,L[3]=2

(若i可以往左延伸到i-j,L[i]==i-j-1,同理R[i]==i+j+1)

如果将要入栈的数比栈顶元素小,那么说明两点:

  1. 对于栈顶的那个下标,已经延伸不到当前点了,就可以确定下其右端点R了
  2. 对于入栈元素,栈顶下标的高度可以让其延伸,那么就使其出栈,直到延伸不到栈顶元素为止,就可以得到将要入栈下标的左端点L

eg:
h[4]=2,比s[3]小,所以s[3]出栈,出栈的时候,s[3]为3,即第3个数不能延伸到4了,所以R[3]=4,这个时候,下标为3高度为3的左右端点已经得出,ans=max(ans,3*(4-2-1))

至于为什么相同的不出栈

其实出不出都可以,就是写法上的区别

首先,同一段上的相同高度,只要有一个正确,就可以了
eg:h数组 3 2 4 2 3 那么len[2]=1~5,len[4]=1~5,其中就算有一个是变小了,也不会影响ans=2*5=10

假设不出栈,h[s[2]]==h[4]==2,本来这个点是可以延伸到的,但是我们不出栈,所以第二个数的L不准确了,但是第一个数的R准确了,那么第一个数因为遇到不相同的出栈了,得到的就是准确的

如果出栈的话,前面那个点的R就不准确了,但是接下来的相同的数的L是准确的,只要遇到一个不相同的使任意一个出栈,那么最后一个相同的数的R就是准确的了,因此得到的还是一段准确的区间

代码

#define N 2009

int n,m;
int M[N][N];
int h[N][N];
int ans;
int s[N],L[N],R[N];

void fin(int row){//相同不出栈
    s[0]=0;int top=0;
    h[row][m+1]=0;//用于得到最后没出栈元素的R
    for(int i=1;i<=m+1;i++){
        int ar=s[top];
        while(h[row][i]<h[row][ar]){
            R[ar]=i;top--;
            ar=s[top];
        }
        L[i]=ar;s[++top]=i;
    }
    for(int i=1;i<=m;i++)
        if(h[row][i])
        ans=max(ans,h[row][i]*(R[i]-L[i]-1));
}

void fin_(int row){//相同出栈
    s[0]=0;int top=0;
    h[row][m+1]=0;
    for(int i=1;i<=m+1;i++){
        int ar=s[top];
        while(h[row][i]<=h[row][ar]){
            R[ar]=i;top--;
            if(top<0)break;//最后m+1的时候把s[0]也出栈了
            ar=s[top];
        }
        L[i]=ar;s[++top]=i;
    }
    for(int i=1;i<=m;i++)
        if(h[row][i])
        ans=max(ans,h[row][i]*(R[i]-L[i]-1));
}

int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        mmm(h,0);ans=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                scanf("%d",&M[i][j]);
                if(M[i][j])h[i][j]=h[i-1][j]+1;
            }
        }
        for(int i=1;i<=n;i++)fin(i);
        printf("%d\n",ans);
    }
}
/*
4 4
0 1 1 1
1 0 1 1
1 1 1 1
1 1 1 0
*/





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值