最大子矩形问题(学习笔记)

总结了几个求最大子矩形(最大正方形)的方法:DP,枚举障碍点,单调栈,悬线法

入门篇 传送门1传送门2双倍经验

题意简述:在一个有障碍点的矩形中找到一个最大正方形

对于数据范围小,直接\(n^2\)算法DP

int bj[1005][1005],f[1005][1005];
int n,m,ans;
int main(){
    n=read();m=read();
    for(int i=1;i<=m;i++){
    	int x=read(),y=read();
    	bj[x][y]=1;
//将障碍点标记为1
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++){
          if(bj[i][j]==0)//如果这个点不是障碍点
              f[i][j]=min(min(f[i][j-1],f[i-1][j]),f[i-1][j-1])+1;
//状态转移方程是本题精髓
//为什么求最大面积会是比较最小值呢?+1是什么意思呢?
//首先明确这里f[i][j]表示的是:
//以第i行第j列为右下角顶点所能构成的最大正方形的边长;
//所以[i][j]这个点一定先要满足不是障碍点这个条件
//那么对于f[i][j]=x,意思就是:
//[i][j]向上x个节点,向左x个节点构成的正方形中无障碍点
//我们要同时满足向上和向左两个条件,所以是取最小值
          ans=max(ans,f[i][j]);//更新最大边长
    }
    printf("%d\n",ans);
    return 0;
}

进阶篇 传送门

题意简述:在一个有障碍点的矩形中找到一个最大子矩形

当矩形边长很大时,我们不能再像上面一样\(n^2\)(n:边长)处理了,此时我们可以考虑从障碍点入手,因为本题中障碍点数较小,所以可以\(n^2\)(n:障碍点数)处理

我是基于一个平面直角坐标系中的矩形来讨论的(每次这种矩形的题目,手动模拟的时候傻傻分不清长和宽)

还是在这里大致讲一下吧(下面也讲得比较详细):

1 按横坐标从小到大枚举障碍点,以每个障碍点为子矩形的一个顶点,分别向右,向左扫描其它的障碍点,同时不断更新上下界.

2 上面这样讨论可能会漏掉左右边界恰与矩形边界重合的子矩形,所以还要考虑到这种情况,可以直接按纵坐标从小到大排序预处理完成.

int L,W,n,ans;
struct cow{
    int x,y;
}a[5001];
//结构体来存障碍点,方便对障碍点排序
bool cmp1(cow b,cow c){
    return b.x<c.x;
}
bool cmp2(cow b,cow c){
    return b.y<c.y;
}
int main(){
    L=read();W=read();n=read();
    for(int i=1;i<=n;i++){
    	a[i].x=read();
    	a[i].y=read();
    }
    a[++n].x=0;a[n].y=0;
    a[++n].x=L;a[n].y=0;
    a[++n].x=0;a[n].y=W;
    a[++n].x=L;a[n].y=W;
//枚举的时候,可能有些子矩形直接与矩形边界重合
//所以我们不妨直接把矩形四个顶点也当做障碍点
	sort(a+1,a+n+1,cmp2);
    for(int i=1;i<=n-1;i++){
    	ans=max(ans,(a[i+1].y-a[i].y)*L);
    }
//先对障碍点按纵坐标从小到大排序
//这里我们枚举的是以上下相邻两个点为上下边界,
//左右边界直接与矩形边界重合的子矩形
    sort(a+1,a+n+1,cmp1);
//障碍点按横坐标从小到大排序
//以a[i]这个障碍点为边界:
    for(int i=1;i<=n;i++){
    int up=W,down=0,wid=L-a[i].x;
//up,down,wid分别是最大子矩形的上界,下界,最大宽度
//初始都赋为(理论上的)最大值
//从左往右扫描每个障碍点:
    	for(int j=i+1;j<=n;j++){
        	if(ans>=wid*(up-down))break;
//ans是当前面积,wid*(up-down)是理论最大面积
        	ans=max(ans,(up-down)*(a[j].x-a[i].x));
//更新最大面积
        	if(a[i].y==a[j].y)break;
//如果两个点在同一条线上,则构不成矩形
            if(a[j].y>a[i].y)
                up=min(up,a[j].y);
     	    if(a[j].y<a[i].y)
     		    down=max(down,a[j].y);
//不断调整子矩形上下边界
    	}
//重置上下界和最大宽度,从右往左扫描(只修改一点点):
        up=W,down=0,wid=a[i].x;
        for(int j=i-1;j>=1;j--){
            if(ans>=wid*(up-down))break;
            ans=max(ans,(up-down)*(a[i].x-a[j].x));
            if(a[i].y==a[j].y)break;
            if(a[j].y>a[i].y)
            	up=min(up,a[j].y);
        	if(a[j].y<a[i].y)
        		down=max(down,a[j].y);
    	}
    }
    printf("%d\n",ans);
    return 0;
}

进阶篇 传送门

题意:在一个有障碍的矩形土地上找到一个最大的子矩形.

本题的核心思想是单调栈,是否记得单调栈的经典例题,不会这道题的戳这里.

在那道经典例题中,题目给我们了一条水平线上的很多矩形,矩形的宽度都是1,要求它们构成的最大子矩形的面积.而这道题中,我们首先拿在手上的是一个\(n*m\)的二维平面,因此我们可以将其视作在n条水平线上,每条水平线上有m个矩形.这样问题就变得和经典例题一模一样了.显而易见,这也是个\(n^2\)算法.

struct A{
    int lon,wid;
}st[1005]; 
int n,m,len[1005][1005];
long long ans;
//单调栈求最大子矩形(这里不讲了)
void B(int x){
    int top=0,width,maxn=0;
    st[++top].lon=len[x][1];
    st[top].wid=1;
    for(int i=2;i<=m;i++){
        width=0;
        while(st[top].lon>=len[x][i]&&top>0){
            width+=st[top].wid;
            maxn=max(maxn,st[top--].lon*width);
        }
        st[++top].lon=len[x][i];
        st[top].wid=width+1;
    }
    width=0;
    while(top>0){
        width+=st[top].wid;
        maxn=max(maxn,st[top--].lon*width);
    }
    if(maxn>ans)ans=maxn;
}
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
        char ch;cin>>ch;
        if(ch=='F')len[i][j]=len[i-1][j]+1;
//转化的关键,相当于转化成了:
//每条水平线上有m个长度不等,宽度为1的矩形
    }
    for(int i=1;i<=n;i++)B(i);
//现在就可以直接开始对每一行分别单调栈求最大子矩形了
    printf("%lld\n",ans*3);
    return 0;
}

进阶篇

Orz国家集训队论文

还是就着上面那道题,谈谈悬线法吧.悬线法,我们可以简单地理解为一根上端点是边界或者障碍点,下端点可以自由伸缩的悬线,不断左右扫描求得最大子矩形的方法.想好好研究的话,推荐上面那篇论文,真的要很耐心地看.

\(a[i][j]=1\)表示该点不是障碍点

\(l,r[i][j]\)表示该点水平方向上所能拓展到的最大的纵坐标处

\(up[i][j]\)表示该点向上所能拓展到的最远距离

理解了这三个数组,下面的代码就很好理解了.

int n,m,ans;
int a[1005][1005],l[1005][1005],r[1005][1005],up[1005][1005];
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++){
	    char ch;cin>>ch;
	    if(ch=='F'){
			a[i][j]=1;
			l[i][j]=r[i][j]=j;
			up[i][j]=1;
	    }
	}
//预处理不是障碍点的点
    for(int i=1;i<=n;i++)
	for(int j=2;j<=m;j++){
	    if(a[i][j]==1&&a[i][j-1]==1)
		l[i][j]=l[i][j-1];
	}
//预处理l数组
    for(int i=1;i<=n;i++)
	for(int j=m-1;j>=1;j--){
	    if(a[i][j]==1&&a[i][j+1]==1)
		r[i][j]=r[i][j+1];
	}
//预处理r数组
    for(int i=2;i<=n;i++)
	for(int j=1;j<=m;j++){
	    if(a[i][j]==1&&a[i-1][j]==1){
			l[i][j]=max(l[i][j],l[i-1][j]);
			r[i][j]=min(r[i][j],r[i-1][j]);
			up[i][j]=up[i-1][j]+1;
	    }
	    ans=max(ans,up[i][j]*(r[i][j]-l[i][j]+1));
	}
//一个max,一个min,根据数组定义来理解即可
    printf("%d\n",ans*3);
    return 0;
}

posted on 2019-01-21 15:02  PPXppx  阅读(223)  评论(0编辑  收藏  举报