单调栈/悬线法--bzoj1057: [ZJOI2007]棋盘制作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sizeof_you/article/details/81952725

传送门

大概一个月以前写的这道题现在才想起来写博客0.0

因为这道题能用两种方法做

solution:

1、单调栈

棋盘上求极大子矩阵的问题,可以先将题目中的条件变形转化成简单的问题,这道题可以对于为0而且横纵坐标奇偶性不同的标为1,为1而且横纵坐标奇偶性相同的标为1;对于为1而且横纵坐标不同的标为0,对于为0而且横纵坐标相同的标为0,转化成最大全0子矩阵问题,再用单调栈首先处理处每一列或者每一行的,然后将矩阵压缩成行,用单调栈就可以实现

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define maxn 2005
using namespace std;
int n,m,a[maxn][maxn],row[maxn][maxn],line[maxn][maxn],stk[maxn],top,to[maxn];
int ans1,ans2;
inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f;
}

void pre(){
	for(int i=1;i<=n;i++)	
		for(int j=1;j<=m;j++)
			if(((i&1)==(j&1) && a[i][j])||((i&1)!=(j&1) && !a[i][j]))
				a[i][j]=1;
			else a[i][j]=0;
} 

void get_row(){
	for(int i=1;i<=n;i++)
		for(int j=m;j;j--)
			if(a[i][j]) row[i][j]=row[i][j+1]+1;
			else row[i][j]=0;
}

void get_line(){
	int tmp,x;
	for(int j=1;j<=m;j++){
		top=0;
		for(int i=1;i<=n;i++){
			x=i;
			while(top>0 && stk[top]>=row[i][j]){
				tmp=min(stk[top],i-to[top]); tmp*=tmp;
				ans1=max(ans1,tmp); tmp=stk[top]*(i-to[top]);
				ans2=max(ans2,tmp); x=min(x,to[top]);
				top--; 
			}
			stk[++top]=row[i][j]; to[top]=x;
		}
	}
}

int main(){
	n=rd(); m=rd();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][j]=rd();
	pre();
	get_row(); get_line();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][j]=!a[i][j];
	get_row(); get_line();
	printf("%d\n%d",ans1,ans2);
}

2、悬线法

神奇的悬线法!以前从来没听过诶,其实也很好懂

悬线法,形象地理解,就是在每个点上悬一条线qwq

悬线法的用途:针对求给定矩阵中满足某条件的极大矩阵,比如“面积最大的长方形、正方形”“周长最长的矩形等等”。可以满足在

时间复杂度为O(M*N)的要求,比一般的枚举高效的多,也易于理解。

引用别人博客的一些理论:

【最大子矩阵问题】

在一个给定的矩形中有一些障碍点,找出内部不包含障碍点的、轮廓与整个矩形平行或重合的最大子矩形。

【定义子矩形】

有效子矩形:内部不包含障碍点的、轮廓与整个矩形平行或重合的子矩形。

极大子矩形:每条边都不能向外扩展的有效子矩形。

最大子矩形:所有有效子矩形中最大的一个(或多个)。

那么这条线怎么悬呢?

从一个点(i,j)往上悬一条线,这条线是满足条件的最长的线,之后我们要悬着这根线左右移动,也就是求最大子矩阵的操作

http://blog.csdn.net/clover_hxy/article/details/50532289?locationNum=1&fps=1

(这个是一篇国家集训队论文《极大化思想解决最大子矩阵问题》)

算法实现:

设 h[i,j]为点(i,j)对应的悬线的长度。

     l[i,j]为点(i,j)对应的悬线向左最多能够移动到的位置/长度。

     r[i,j]为点(i,j)对应的悬线向右最多能够移动到的位置/长度。

我们可以一次预处理就能求出这几个数组,在处理问题的时候有一个模板

考虑递推的过程,从上往下,如果这里有悬线,那么它往左延伸的长度一定有一个最小值,我们可以一行一行取l的min(长度和位置略不同,这里用的是长度),同理取r的min,这之后,以这一行为结尾的一个最大长方形矩阵就是(h[i][j]+1)*(r[i][j]+l[i][j]+1) 这个式子根据自己数组定义的不同和处理方式的不同可能会不一样。如果是正方形矩阵只要把这两个值取min再乘方就好了

代码如下:

//悬线法 
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 2005
using namespace std;
int n,m,a[maxn][maxn],ans1,ans2;
int h[maxn][maxn],r[maxn][maxn],l[maxn][maxn];
 
inline int rd(){
    int x=0,f=1;char c=' ';
    while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
    while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
    return x*f;
}
 
void pre(){
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){ 
            if(a[i][j]^a[i][j-1]) l[i][j]=l[i][j-1]+1;
            if(a[i][m-j+1]^a[i][m-j+2]) r[i][m-j+1]=r[i][m-j+2]+1;
            if(a[i][j]^a[i-1][j]) h[i][j]=h[i-1][j]+1;
        } 
}
 
int main(){
    n=rd(); m=rd();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=rd();
    pre();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++){
            if(h[i][j]){
                l[i][j]=min(l[i][j],l[i-1][j]);
                r[i][j]=min(r[i][j],r[i-1][j]);
            }
            int tmp=min(h[i][j]+1,(l[i][j]+r[i][j]+1));
            ans1=max(ans1,tmp*tmp);
            ans2=max(ans2,(h[i][j]+1)*(l[i][j]+r[i][j]+1));
        }
    printf("%d\n%d",ans1,ans2);
    return 0;
}


猜你喜欢

转载自blog.csdn.net/sizeof_you/article/details/81952725