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


    总结了几个求最大子矩形(最大正方形)的方法: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
    ",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
    ",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
    ",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
    ",ans*3);
        return 0;
    }
    
    
  • 相关阅读:
    用redis实现分布式锁
    mac下Nginx+lua模块编译安装
    ESXi5 中克隆Linux虚拟主机的网络配置
    DOS命令中的For
    让delphi解析chrome扩展的native应用
    C语言 cgi(3)
    C语言 cgi(2)
    C语言cgi(1)
    c++ input,output
    Array of Objects
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10298759.html
Copyright © 2020-2023  润新知