spoj上的MINSUB ,Wnnafly Union给的题
题意是给定一个由非负数组成的矩阵和整数K,要求找出一个子矩阵面积不小于K,且使得该子矩阵中的元素的最小值最大化。子矩阵面积为该矩阵的行数*列数。
题目链接:http://www.spoj.com/problems/MINSUB/
最小值最大化,这种问题很容易想到二分求解。但是怎么具体去求解这个问题呢,想了好多办法,什么枚举行数列数然后用单调队列去维护这个子矩阵的最小值啦,但是都不太能实现。看了Q神的题解,Q神的思路是二分枚举这个答案然后把矩阵中≥该值的元素设为1,其他设为0。具体问题就转化为了求这个01矩阵的面积最大值。
求01矩阵面积最大值可以用单调栈去求解。我们知道单调栈最经典的应用就是:
假设有一个大小为n的数组a[],对于数组中的任意一个数字ai,我们可以在O(n)的时间里求解出从ai往左最多有多少个连续的数字≤ai(这里我们定义为ai向左延伸),从ai往右最多有多少个连续的数字≤ai。具体操作就是维护一个严格单调递减的栈,栈顶元素是最小值,栈中元素记录了三个东西(1.从这个元素最多能往左延伸到的位置,2.这个元素 能向右延伸到的位置,3.这个元素本身的值)。当一个元素进栈时,它如果大于等于栈顶的元素,那么栈顶元素弹出,且将其最左能延伸到的位置赋给当前元素,同时,被弹出的元素能向右延伸的位置就是当前元素的位置的前一个位置(因为当前元素大于被弹出元素,所以最多能向右延伸到当前元素的前一个位置。如果当前元素=被弹出元素 ,那么被弹出的元素的能向右延伸的最远位置实际上等价于当前元素向右延伸的位置) ,当数组中最后一个数字进栈结束后 , 此时栈中还有元素。为了方便我们可以在数组最后补上一个无穷小,然后进栈(把栈中元素全弹出来)。
然后利用单调栈我们枚举一下这个要求的子矩阵的下边界,然后把其当成一个大小等于原矩阵长度的数组。
例如 这样一个01矩阵
0 0 0 0 1
0 1 1 1 1
1 1 1 1 1
0 1 0 1 0 我们枚举下边界然后求下边界向上连续是1的个数。 如这个矩阵我们得到 0 3 0 3 0,1 2 2 2 3 , 0 1 1 1 2 , 0 0 0 0 1 ,通过单调栈我们算一下每个值乘以该值向左和向右最多延伸的距离之和,就是这个01矩阵的面积了。
题目里的二分是整数,太菜了之前只做过实数二分的……所以二分这里一直写残了,
补上一个整数二分的模板吧............
求最大值的
while(l < r)
{
mid = ( l + r +1) / 2;
if(check(mid))
l = mid;
else r = mid - 1 ;
}
求最小值的
while(l < r)
{
mid = ( l + r ) / 2;
if(check(mid))
r = mid;
else l = mid + 1 ;
}
整道题的代码
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<stdlib.h>
#include<map>
#include<time.h>
#include<cstdio>
#include<vector>
#include<stack>
#include<set>
#include<ctype.h>
#include<fstream>
#include<queue>
#include<iostream>
using namespace std;
const int INF=0x3f3f3f3f;
const int MOD=10000;
const double PI=acos(-1.0);
#define clrI(x) memset(x,-1,sizeof(x))
#define clr0(x) memset(x,0,sizeof x)
#define clr1(x) memset(x,INF,sizeof x)
#define clr2(x) memset(x,-INF,sizeof x)
#define writeln(x) cout<<x<<endl
#define readln(x) cin>>x
#define LONG long long
#define rep(a,b,c) for(LONG a=b;a<=c;a++ )
#define rrep(a,b,c) for(LONG a=b;a>=c;a--)
#define EPS 1e-10
int read(void)
{
char c=getchar();while(c<'0'||c>'9')c=getchar();
int t=c-48;for(c=getchar();c>='0'&&c<='9';c=getchar())t=(t<<1)+(t<<3)+c-48;
return t;
}
struct Stack{
int val ;
int l ,r;
}monostack[1200];
int p1 , p2;
int res = 0 ;
void push(Stack x)
{
for(;;)
{
if(x.val > monostack[p2].val || p2 < p1)
break ;
x.l = monostack[p2].l ;
monostack[p2].r = x.r - 1 ;
res = max(res , monostack[p2].val * (monostack[p2].r - (monostack[p2].l - 1)) ) ;
p2 -- ;
}
p2 ++ ;
monostack[p2] = x;
}
int matr[1200][1200];
int tmp_Matr[1200][1200];
int main()
{
int T;
T = read();
while(T--)
{
int r, c , k ;
scanf("%d%d%d",&r,&c,&k) ;
int maxn = 0;
int n = 0;
for(int i = 1 ; i <= r; i++ )
for(int j =1 ;j<= c ;++ j)
{
matr[i][j] = read();
maxn = max(maxn , matr[i][j]);
}
if(r == 0 || c == 0)
{
printf("0 0\n");
continue;
}
if(k == 0)
{
k = 1;
}
int left = 0 , right = 1100000000 ;
int mid = 1 ;
res = r* c;
while(left + 1 < right )
{
mid = (left + right ) / 2;
int tmp = mid ;
for(int i = 1 ; i<=r ;++ i)
for(int j = 1; j <= c; ++ j)
if(matr[i][j] < tmp)
tmp_Matr[i][j] = 0;
else tmp_Matr[i][j ] = tmp_Matr[i-1][j] + 1;
res = 0 ;
for(int i = 1; i<= r; ++i )
{
p1 = 1 , p2 = 0;
for(int j =1 ; j <= c;++ j)
{
Stack temp ;
temp.val = tmp_Matr[i][j] ;
temp.l = j ;
temp.r = j ;
push(temp) ;
}
Stack temp ;
temp.val = 0;
temp.l = c + 1 ;
temp.r = c + 1;
push(temp) ;
}
if( res >= k)
left = mid ;
else if(res < k ) right = mid - 1;
}
int tmp =left ;
for(int i = 1 ; i<=r ;++ i)
for(int j = 1; j <= c; ++ j)
if(matr[i][j] < tmp)
tmp_Matr[i][j] = 0;
else tmp_Matr[i][j ] = tmp_Matr[i-1][j] + 1;
res = 0 ;
for(int i = 1; i<= r; ++i )
{
p1 = 1 , p2 = 0;
for(int j =1 ; j <= c;++ j)
{
Stack temp ;
temp.val = tmp_Matr[i][j] ;
temp.l = j ;
temp.r = j ;
push(temp) ;
}
Stack temp ;
temp.val = 0;
temp.l = c + 1 ;
temp.r = c + 1;
push(temp) ;
}
int res1 = res ;
tmp = right ;
for(int i = 1 ; i<=r ;++ i)
for(int j = 1; j <= c; ++ j)
if(matr[i][j] < tmp)
tmp_Matr[i][j] = 0;
else tmp_Matr[i][j ] = tmp_Matr[i-1][j] + 1;
res = 0 ;
for(int i = 1; i<= r; ++i )
{
p1 = 1 , p2 = 0;
for(int j =1 ; j <= c;++ j)
{
Stack temp ;
temp.val = tmp_Matr[i][j] ;
temp.l = j ;
temp.r = j ;
push(temp) ;
}
Stack temp ;
temp.val = 0;
temp.l = c + 1 ;
temp.r = c + 1;
push(temp) ;
}
if(res >= k) left = right ,res1 = res;
cout<<left<<" "<< res1 <<endl;
}
}