zoukankan      html  css  js  c++  java
  • 最大子矩阵问题&&悬线法 学习小结

    最近在写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

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

  • 相关阅读:
    Java实现第九届蓝桥杯第几个幸运数字
    Java实现第九届蓝桥杯第几个幸运数字
    Java实现第九届蓝桥杯字母阵列
    Java实现第九届蓝桥杯字母阵列
    为什么mysql设置了密码之后,本地还可以直接访问,不需要输入密码就可以登录数据库了?
    centos7破解mariadb密码
    centos7使用无线wifi连接
    centos7中firewall防火墙命令详解
    CentOS 7 为firewalld添加开放端口及相关资料
    Mysql中较为复杂的分组统计去重复值
  • 原文地址:https://www.cnblogs.com/Tyouchie/p/11382288.html
Copyright © 2011-2022 走看看