悬线法dp

悬线法dp

出处:【转】【最大子矩阵问题】【悬线法】 学习笔记
注:已经有大佬的发了详解,搬过来方便以后复习

学习材料:王知昆《浅谈用极大化思想解决最大子矩阵问题》

【最大子矩阵问题】

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

【定义子矩形】

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

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

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

【极大化思想】

在一个有障碍点的矩形中最大子矩形一定是极大子矩形。

设计算法的思路:枚举所有的极大子矩形,找到最大子矩形。

设NM分别为整个矩形的长和宽,S为内部的障碍点数。

【算法1】

时间复杂度:O(S^2) 空间复杂度:O(S)

由于极大子矩形的每一条边都不能向外扩展,那么极大子矩阵的每条边要么覆盖了障碍点,要么与整个矩形的边界重合

基本算法:枚举上下左右四个边界,然后判断组成的矩形是否是有效子矩形。

复杂度:O(S^5) 可以改进的地方:产生了大量的无效子矩形。

初步改进的算法:枚举左右边界,然后对处在边界内的点排序,每两个相邻的点和左右边界组成一个矩形。

复杂度:O(S^3) 可以改进的地方:枚举了部分不是极大子矩形的情况。

综上,设计算法的方向:

1、保证每一个枚举的矩形都是有效的。

2、保证每一个枚举的矩形都是极大的。

算法的过程:

枚举极大子矩形的左边界——>根据确定的左边界,找出相关的极大子矩形——>检查和处理遗漏的情况

(1)按照横坐标从小到大的顺序将所有的点编号为1,2,3…

(2)首先选取1号点作为要枚举的极大子矩形的左边界,设定上下边界为矩形的上下边界

(3)从左到右扫描,第一次到2号点,确定一个极大子矩形,修改上下边界;第二次找到3号点,以此类推。

(4)将左边界移动到2号点,3号点,,,以同样的方法枚举

遗漏的情况:

1、矩形的左边界与整个矩形的左边界重合。解决方法:用类似的方法从左到右扫一遍

2、矩形的左边界与整个矩形的左边界重合,且矩形的右边界与整个矩形的右边界重合。解决方法:预处理时增加特殊判断。

优点:利用的极大化思想,复杂度可以接受,编程实现简单。

缺点:使用有一定的局限性,不适合障碍点较密集的情况。

【算法2】

时间复杂度O(NM) 空间复杂度O(NM)

定义

有效竖线:除了两个端点外,不覆盖任何一个障碍点的竖直线段。

悬线:上端覆盖了一个障碍点或者到达整个矩形上边界的有效线段。

每个悬线都与它底部的点一一对应,矩形中的每一个点(矩形顶部的点除外)都对应了一个悬线。

悬线的个数=(N-1)*M;

如果把一个极大子矩形按照横坐标的不同切割成多个与y轴平行的线段,那么其中至少有一个悬线。

如果把一个悬线向左右两个方向尽可能的移动,那么就得到了一个矩形,我们称它为悬线对应的矩形。

悬线对应的矩形不一定是极大子矩形,因为下边界可能还可以向下扩展。

设计算法:

原理:所有悬线对应矩形的集合一定包含了极大子矩形的集合。
通过枚举所有的悬线,找出所有的极大子矩形。
算法规模:
悬线个数=(N-1)×M
极大子矩形个数≤悬线个数

具体方法:
设 H[i,j]为点(i,j)对应的悬线的长度。
L[i,j]为点(i,j)对应的悬线向左最多能够移动到的位置。
R[i,j]为点(i,j)对应的悬线向右最多能够移动到的位置。

!考虑点(i,j)对应的悬线与点(i-1,j)对应的悬线的关系(递推思想):

如果(i-1,j)为障碍点,那么,如图所示,(i,j)对应的悬线长度1,左右能移动到的位置是整个矩形的左右边界。
即 H[i,j]=1,
L[i,j]=0,R[i,j]=m

如果(i-1,j)不是障碍点,那么,如图所示,(i,j)对应的悬线长度为(i-1,j)对应的悬线长度+1。
即 H[i,j]=H[i-1,j]+1

•如果(i-1,j)不是障碍点,那么,如图所示,(i,j)对应的悬线左右能移动的位置要在(i-1,j)的基础上变化。
L[i-1,j]
L[i,j]=max
(i-1,j)左边第一个障碍点的位置
•同理,也可以得到R[i,j]的递推式
R[i-1,j]
R[i,j]=min
(i-1,j)右边第一个障碍点的位置

优点: 复杂度与障碍点个数没有直接关系。
缺点:障碍点少时处理较复杂,不如算法1。

代码实现具体见WC2002 奶牛浴场(算法1)

codevs1159最大全0子矩阵(算法2)

//悬线法dp模板 最大全1正方形
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int n,t,ans;
int a[N][N];
int l[N][N],r[N][N],up[N][N];

int main(){
    scanf("%d%d",&n,&t);
    while(t--){
        int x,y; scanf("%d%d",&x,&y);
        a[x][y]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            l[i][j]=r[i][j]=j;
            up[i][j]=1;
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=2;j<=n;j++){
            if(!a[i][j]&&!a[i][j-1]) l[i][j]=l[i][j-1];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=n-1;j>=1;j--){
            if(!a[i][j]&&!a[i][j+1]) r[i][j]=r[i][j+1];
        }
    }

    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(i>1&&!a[i][j]&&!a[i-1][j]){
                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;
            }
            int a=r[i][j]-l[i][j]+1;
            int b=min(a,up[i][j]);
            ans=max(ans,b);
        }
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值