SPOJ-MINSUB - Largest Submatrix(单调栈,二分,好题)

题目链接

MINSUB - Largest Submatrix

no tags 

You are given an matrix M (consisting of nonnegative integers) and an integer K.  For any submatrix of M' of M define min(M') to be the minimum value of all the entries of M'.  Now your task is simple:  find the maximum value of min(M') where M' is a submatrix of M of area at least K (where the area of a submatrix is equal to the number of rows times the number of columns it has).

Input

The first line contains a single integer T (T ≤ 10) denoting the number of test cases, T test cases follow.  Each test case starts with a line containing three integers, R (R ≤ 1000), C (C ≤ 1000) and K (K ≤ R * C) which represent the number of rows, columns of the matrix and the parameter K.  Then follow R lines each containing C nonnegative integers, representing the elements of the matrix M.  Each element of M is ≤ 10^9

Output

For each test case output two integers:  the maximum value of min(M'), where M' is a submatrix of M of area at least K, and the maximum area of a submatrix which attains the maximum value of min(M').  Output a single space between the two integers.

Example

Input:
2
2 2 2
1 1
1 1
3 3 2
1 2 3
4 5 6
7 8 9

Output:
1 4
8 2


题意:

给定一个由非负数组成的矩阵M,和一个整数K,对于矩阵M的子矩阵M’,定义min(M’)M'矩阵中元素的最小值。

我们需要找出这样一个子矩阵,该矩阵的面积至少为K,且min(M’)最大化。面积的定义为该矩阵的行数*列数。求出min(M'),并给出使得min(M')为该值时面积的最大值。


题解:

这类问题都是可以二分答案的。把小于二分值的位置设为0,其他设为1,那么问题就变成了求全为1的子矩阵的最大面积,这件事情可以用单调栈搞(方法类似于http://www.cnblogs.com/ziyi--caolu/archive/2013/06/23/3151556.html ,事先统计出每个位置向左有多少个1)。可以预处理出每个点(i,j)的左边有多少个连续的1(记为f[i][j]),然后枚举每一列,那么这一列就可以类似与单调栈一样处理算出能上下延伸多少,做法和一位数组的单调栈一样。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const int inf=0x3fffffff;
const ll mod=1000000007;
const int maxn=1000+100;
int r,c,k;
int a[maxn][maxn];
struct node
{
    int pre,next,v;
    node(int a):v(a),pre(1),next(1) {}
};
stack<node> s;
int f[maxn][maxn];
int cal(int x)
{
    int ans=0;
    while(!s.empty()) s.pop();
    memset(f,0,sizeof(f));
    rep(i,1,r+1) rep(j,1,c+1)
    {
        if(a[i][j]>=x) f[i][j]=f[i][j-1]+1;
        else f[i][j]=0;
    }
    rep(j,1,c+1)
    {
        s.push(node(f[1][j]));
        rep(i,2,r+1)
        {
            node t=node(f[i][j]);
            while(!s.empty()&&s.top().v>=t.v)  //此处>或>=都可以
            {
                node tt=s.top();
                s.pop();
                t.pre+=tt.pre;
                if(!s.empty()) s.top().next+=tt.next;
                ans=max(ans,tt.v*(tt.next+tt.pre-1));
            }
            s.push(t);
        }
        while(!s.empty())
        {
            node tt=s.top();
            s.pop();
            if(!s.empty()) s.top().next+=tt.next;
            ans=max(ans,tt.v*(tt.next+tt.pre-1));
        }
    }
    return ans;
}
int main()
{
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d%d",&r,&c,&k);
        int mi=inf,mx=-inf;
        rep(i,1,r+1) rep(j,1,c+1) scanf("%d",&a[i][j]),mi=min(mi,a[i][j]),mx=max(mx,a[i][j]);
        int l=mi,r=mx,ans=0;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(cal(mid)>=k) l=mid+1,ans=mid;
            else r=mid-1;
        }
        printf("%d %d\n",ans,cal(ans));
    }
    return 0;
}

后来,在深刻理解单调栈的原理之后,我又学会了单调栈的另一种写法,又短又快~

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const int inf=0x3fffffff;
const ll mod=1000000007;
const int maxn=1000+10;
int a[maxn][maxn];
int s[maxn];  //此处栈中存的是数组中的下标
int f[maxn][maxn];
int n,m,k;
int check(int x)
{
    int ans=0;
    rep(i,1,n+1) rep(j,1,m+1) f[i][j]=a[i][j]>=x? f[i-1][j]+1:0; //此处与题解不同,这里是向上扩展
    rep(i,1,n+1)
    {
        int top=0;
        f[i][m+1]=0;
        rep(j,1,m+2)
        {
            while(top&&f[i][j]<f[i][s[top]])
            {
                ans=max(ans,f[i][s[top]]*(j-1-s[top-1]));
                top--;
            }
            s[++top]=j;
        }
    }
    return ans;
}

int main()
{
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d%d%d",&n,&m,&k);
        int mx=0,mi=2e9;
        rep(i,1,n+1) rep(j,1,m+1) scanf("%d",&a[i][j]),mx=max(mx,a[i][j]),mi=min(mi,a[i][j]);
        int l=mi,r=mx;
        int ans=0;
        while(l<=r)
        {
            int m=(l+r)/2;
            if(check(m)>=k)
            {
                ans=m;
                l=m+1;
            }
            else r=m-1;
        }
        printf("%d %d\n",ans,check(ans));
    }
    return 0;
}












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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值