最大子矩阵问题&&悬线法 学习小结

最近在写dp的题目

但是我这个同学又又又又生病了

学习了一下多叉树的背包问题和最大子矩阵的问题,还有攒了几道期望,先总结一下矩阵问题;

问题模型:

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

//根据问题我们改动程序,我们现在学习这种解决问题的思想;

有关论文:王知昆《浅谈用极大化思想解决最大子矩阵问题》;

那么我们先来讲一下什么是极大化思想;

先介绍一下子矩阵的概念;

有效子矩形:内部不包含障碍点的(或者满足黑白相间)、轮廓与整个矩形平行或重合的子矩形。

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

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

满足:

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

枚举所有的极大子矩形,找到最大子矩形。

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

我们知道极大子矩阵一定不能再向外扩展,所以极大子矩阵的边界要么为障碍点所处位置,要么与位于整个矩阵的边界;

我们可以枚举左右上下四个边界,然后判断组成的矩阵是否具有有效子矩阵;

但是枚举了很多无效的子矩阵,复杂度(S^5)

我们可以枚举左右边界,然后将边界内的点排序,每个相邻的点左右边界组成一个矩阵; 我们将复杂度降至(S^3)

但是我们枚举中一部分不是极大子矩阵;

我们要做的就是让枚举的每一个矩阵都是有效的并且是极大的;

我们可以设计以下算法:

1/将所有点按照横坐标排序,并编号1,2,3.....n;

2/枚举每一个点作为左边界,设定此时上下边界为矩阵的上下边界;

3.扫描后面的点作为右边界,确定一个极大子矩阵,根据纵坐标的关系,我们修改当前的上下边界;

4.以此类推,枚举所有的点;

但是我们还要额外考虑一些情况:

1.矩形的左边界和矩阵的左边界重合;

这依旧可以分类讨论(1),左边界与矩阵左边界重合,而右边界覆盖了一个障碍点,我们可以从右向左是扫描,把当前点作为右边界的情况;

(2)左右边界都与矩阵的左右边界重合,我们可以预处理出来,具体做法就是按照纵坐标从小到达排序,相邻两个点纵坐标上下边界为上下边界,而矩阵左右边界作为边界,

这也是我们需要枚举到的;

但是这个题适用于障碍点较少的矩阵中,代码容易;时间复杂度(NM) 空间复杂度(NM)

除了这种方法,对于障碍点比较密集的情况,我们可以采取悬线法解决;

我们定义 :

有效竖线:对于上下两个端点不包含障碍点的竖直直线;

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

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

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

对于一个极大子矩阵按照横坐标不同我们切割多个与y轴平行的线段,那么其中至少有一个悬线;

我们将一个悬线对应向左右拓展,就得到一个矩阵,悬线对应的矩阵不一定是极大矩阵,下坐标可能拓展;

所以极大矩阵个数<=悬先个数;

我们枚举每一个悬线,找到极大子矩阵;

我们设left[i][j]为(i,j)对应的悬线做多能向左拓展的位置;

同样的,right[i][j]为(i,j)对应的悬线做多能向右拓展的位置;

up[i][j]为]为(i,j)对应的悬线的长度;

我们考虑(i,j)和(i-1,j)的对应关系;

如果(i-1,j)是障碍点,那么(i,j)对应悬线长度为1,左右边界为整个矩阵的左右边界;

如果(i-1,j)步是障碍点,那么(i,j)对应的悬线长度为up[i][j]=up[i-1][j]+1,对应我们改变left[i][j]为max(left[i][j],left[i-1][j]),right=min(right[i][j],right[i-1][j]);

注意初始化;以下代码是寻找全为0的最大矩阵,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]==0&&a[i][j-1]==0) 
                    l[i][j]=l[i][j-1];
        for(int i=1;i<=n;i++)
            for(int j=n-1;j>0;j--)
                if(a[i][j]==0&&a[i][j+1]==0)
                    r[i][j]=r[i][j+1];    

实现起来比较容易,我们接下来看几道例题;

luogu1578奶牛浴室

这里用到第一种极大化思想,按照前面所讲的排序,我中间对于j特判了==n和==1的情况,没有的话会wa一个点,也就是极大化矩阵是左右边界的情况,其实后来有的题解并没有这一点

有待解决这个问题,而且洛谷第一篇题解我测了以下一个数据,被hack掉了;

题目第是11组数据:

6 4
4
1 2
4 1
4 3
2 1

输出应该是10

但是第一篇题解是9;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
int L,H,n;
struct node {
    int x,y;
} a[5010];
inline bool cmp1(node A,node B) {
    if(A.x!=B.x) {
        return A.x<B.x;
    }
    else {
        return A.y<B.y;
    }
}
inline bool cmp2(node A,node B) {
    if(A.y!=B.y) {
        return A.y<B.y;
    } 
    else {
        return A.x<B.x;
    }
}
int main() {
    read(L); read(H); read(n);
    for(int i=1;i<=n;i++) {
        read(a[i].x); read(a[i].y);
    }
    a[++n].x=0,a[n].y=0;
    a[++n].x=0,a[n].y=H;
    a[++n].x=L,a[n].y=0;
    a[++n].x=L,a[n].y=H;
    sort(a+1,a+1+n,cmp1);
    int ans=0;
    for(int i=1;i<=n;i++) {
        int l=0,h=H,maxl=L-a[i].x;
        for(int j=i+1;j<=n;j++) {
            if(j==n) {//这个特判不写不过第11组?? 
                ans=max(ans,maxl*(h-l));
            } else {
                if(a[j].y<=h&&a[j].y>=l) {
                    if(maxl*(h-l)<=ans) {
                        break;
                    }
                    ans=max(ans,(a[j].x-a[i].x)*(h-l));
                    if(a[j].y==a[i].y) {
                        break;
                    }
                    if(a[j].y>a[i].y) {
                        h=min(h,a[j].y);
                    } else {
                        l=max(l,a[j].y);
                    }
                }
            }
        }
        l=0,h=H,maxl=a[i].x;
        for(int j=i-1; j>=1; j--) {
            if(a[j].y<=h&&a[j].y>=l) {
                if(maxl*(h-l)<=ans) {
                    break;
                }
                ans=max(ans,(a[i].x-a[j].x)*(h-l));
                if(a[j].y==a[i].y) {
                    break;
                }
                if(a[j].y>a[i].y) {
                    h=min(h,a[j].y);
                } else {
                    l=max(l,a[j].y);
                }
            }
            if(j==1) {
                ans=max(ans,maxl*(h-l));
            }
        }
    }
    sort(a+1,a+1+n,cmp2);
    for(int i=1;i<n;i++) {
        ans=max(ans,(a[i+1].y-a[i].y)*L);
    }
    printf("%d",ans);
    return 0;
}
View Code

棋盘制作

给定矩阵,寻找一个黑白相间最大矩形和最大正方形;

那么我们只需要特判不等于就可以了,这里利用了悬线法;

代码也是比较方便好写,我个人比较喜欢这一种;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
const int N=2010; 
int n,m;
int a[N][N],l[N][N],r[N][N],up[N][N],ans1,ans2;
int main() {
    read(n); read(m);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            read(a[i][j]);
            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]!=a[i][j-1]) {
                l[i][j]=l[i][j-1];
            }
        }
    }
    for(int i=1;i<=n;i++) {
        for(int j=m-1;j>0;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<=m;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]);
            ans1=max(ans1,b*b);
            ans2=max(ans2,a*up[i][j]);
        }
    }
    cout<<ans1<<endl<<ans2<<endl;
    return 0;
}
View Code

玉蟾宫

寻找最大全0矩形,裸题,和上题目一样;按照题目要求答案*3;

#include<bits/stdc++.h>
using namespace std;
int n,m,a[1010][1010],l[1010][1010],r[1010][1010],up[1010][1010];
char ch[1010][1010]; 
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
int main() {
    //freopen("1.in","r",stdin);
    read(n); read(m);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            cin>>ch[i][j];
            if(ch[i][j]=='F') a[i][j]=0;
            else 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]==0&&a[i][j-1]==0) 
                l[i][j]=l[i][j-1];
    for(int i=1;i<=n;i++)
        for(int j=m-1;j>0;j--)
            if(a[i][j]==0&&a[i][j+1]==0)
                r[i][j]=r[i][j+1];
    int ans=0;
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++){
            if(i>1&&a[i][j]==0&&a[i-1][j]==0){
                r[i][j]=min(r[i][j],r[i-1][j]);
                l[i][j]=max(l[i][j],l[i-1][j]);
                up[i][j]=up[i-1][j]+1;
            }
        ans=max(ans,(r[i][j]-l[i][j]+1)*up[i][j]);
        }
    }
    cout<<ans*3<<endl;
} 
View Code

巨大的牛棚

裸题,寻找没有障碍点,这大概好几倍经验了;是正方形;

#include<bits/stdc++.h>
using namespace std;
int n,m,T,x,y,a[1010][1010],l[1010][1010],r[1010][1010],up[1010][1010];
template<typename T>inline void read(T &x) {
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
int main() {
    //freopen("1.in","r",stdin);
    read(n);
    read(T);
    memset(a,0,sizeof(a));
    for(int i=1;i<=T;i++) {
        read(x); read(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]==0&&a[i][j-1]==0) 
                l[i][j]=l[i][j-1];
    for(int i=1;i<=n;i++)
        for(int j=n-1;j>0;j--)
            if(a[i][j]==0&&a[i][j+1]==0)
                r[i][j]=r[i][j+1];
    int ans=0;
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=n;j++) {
            if(i>1&&a[i][j]==0&&a[i-1][j]==0){
                r[i][j]=min(r[i][j],r[i-1][j]);
                l[i][j]=max(l[i][j],l[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);
        }
    }
    cout<<ans<<endl;
    return 0;
} 
View Code

最大正方形

和棋盘制作一样,再加一倍经验;

#include<bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T &x) {
    x=0;
    T f=1,ch=getchar();
    while (!isdigit(ch)) {if(ch=='-') f=-1; ch=getchar();}
    while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
    x*=f;
}
const int N=2010; 
int n,m;
int a[N][N],l[N][N],r[N][N],up[N][N],ans1,ans2;
int main() {
    read(n); read(m);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            read(a[i][j]);
            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]!=a[i][j-1]) {
                l[i][j]=l[i][j-1];
            }
        }
    }
    for(int i=1;i<=n;i++) {
        for(int j=m-1;j>0;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<=m;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]);
            ans1=max(ans1,b);
            //ans2=max(ans2,a*up[i][j]);
        }
    }
    cout<<ans1; 
    return 0;
}
View Code

我认为是很好理解的,代码也很好写;

posted @ 2019-08-20 13:10  Tyouchie  阅读(581)  评论(0编辑  收藏  举报