动态规划之悬线法模板(最大子矩阵问题)

Posted 满天星!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划之悬线法模板(最大子矩阵问题)相关的知识,希望对你有一定的参考价值。

悬线法的用途

针对求给定矩阵中满足某条件的极大矩阵,比如“面积最大的长方形、正方形”“周长最长的矩形等等”。可以满足在时间复杂度为O(M*N)的要求,比一般的枚举高效的多,也易于理解。

悬线法思路

一般地,我们有一张n∗m的图,里面有一些障碍,我们想要求出一个最大的子矩形,使得它里面没有任何障碍(或者说,使它满足某个条件)。

我们考虑从每个点向上作一条射线,这条线如果遇到一个障碍或者是矩形的上边界就停下。这条线,就叫做悬线。我们把这条线尽可能地往左右移动(尽可能指的是不遇到障碍),就可以围成一个极大子矩形,这个子矩形是这条悬线所能构成的最大的子矩形。

显然,最大子矩形是属于所有悬线能构成的极大子矩形的集合里的。

于是,我们只要枚举每个悬线,O(1)地算出每个极大子矩形的面积,然后取一个max就行了。

考虑每一个点和每一条悬线一一对应,且一共有n∗m个点,所以复杂度就是O(nm)。

那么,问题就转化为如何O(1)地算出每个极大子矩形的面积。

先定义

Left[i][j]:代表从(i,j)能到达的最左位置
Right[i][j]:代表从(i,j)能到达的最右位置
Up[i][j]:代表从(i,j)向上扩展最长长度.

我们考虑递推,设Up[i][j]表示(i,j)的悬线长度。

Up[i][j] = Up[i - 1][j] + 1;

然后我们再考虑左右边界,记为Left[i][j],Right[i][j]进行递推。

 Left[i][j] = max(Left[i - 1][j], Left[i][j])
 Right[i][j] = min(Right[i - 1][j], Right[i][j])

最后,我们枚举每个点,统计一下答案

算法图解
在这里插入图片描述在这里插入图片描述

例题讲解
P1169棋盘制作

题意:

给你一个01棋盘,求其中01交错的最大正方形与矩形。

思路:
动态规划之悬线法
if语句执行的条件是a[i][j]!=a[i-1][j],即只有满足条件的情况下我们才能更改当前位置(i,j)的left数组与right数组,up数组.
而不满足条件时,我们当前位置(i,j)(i,j)的left,right,up数组并不会改变.

if(i>1&&a[i][j]!=a[i-1][j])
{
	left[i][j]=max(left[i][j],left[i-1][j]);
	right[i][j]=min(right[i][j],right[i-1][j]);
	up[i][j]=up[i-1][j]+1;
}

注意点:

1.c++iostream中有一个left函数,所以请不要引用bits/stdc++.h或iostream

2.求正方形的面积就是长方形较短边长的平方

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=2500;
int Left[N][N],Right[N][N],Up[N][N],a[N][N];
int n,m,sz,sj;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        cin>>a[i][j];
        Up[i][j]=1;
        Left[i][j]=Right[i][j]=j; // 初始化数组
    }
    for(int i=1;i<=n;i++)
    for(int j=2;j<=m;j++)
    {
        if(a[i][j]!=a[i][j-1])
        Left[i][j]=Left[i][j-1];  //预处理左边界,left初值,即(i,j)左的最大宽度
    }
    for(int i=1;i<=n;i++)
    for(int j=m-1;j>0;j--)
    {
        if(a[i][j]!=a[i][j+1])
        Right[i][j]=Right[i][j+1]; //预处理右边界,right初值,即(i,j)右的最大宽度
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        if(i>1&&a[i][j]!=a[i-1][j])
        {
            Left[i][j]=max(Left[i][j],Left[i-1][j]);
            Right[i][j]=min(Right[i][j],Right[i-1][j]);
            Up[i][j]=Up[i-1][j]+1;
        }
     int la=Right[i][j]-Left[i][j]+1; //横向长度
     int lb=min(la,Up[i][j]);  //竖向长度
     sz=max(sz,lb*lb);
     sj=max(sj,la*Up[i][j]);

    }
    cout<<sz<<endl<<sj<<endl;
}

P4147 玉蟾宫

题意:

求出能被“F”填满的最大矩形的面积,请输出最大矩形面积的三倍。

思路:
悬线法
设h(i,j)表示以(i,j)为下端点的悬线的最长长度。预处理l(i,j)和r(i,j)它们分别表示点(i,j)能扩展到的左边和右边的最近的障碍。
L(i,j)和R(i,j)分别表示使悬线有此长度的左边最近的障碍和右边最近的障碍。
答案即为max(h(i,j)*(R(i,j)-L(i,j)+1);对于本题,要求输出答案乘3后的值。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=4100;
int l[N][N],r[N][N],u[N][N],a[N][N];
int n,m,t,s;
int main()
{
    cin>>n>>m;
    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,u[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]; //预处理左边界,left初值,即(i,j)左的最大宽度
    }
    for(int i=1;i<=n;i++)
    for(int j=m-1;j>=1;j--)
    {
      if(a[i][j]&&a[i][j+1])
      r[i][j]=r[i][j+1]; //预处理右边界,right初值,即(i,j)右的最大宽度
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
     if(a[i][j])
        {
        if(i>1&&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]);
           u[i][j]=u[i-1][j]+1;
        }
        int d=r[i][j]-l[i][j]+1; //横向长度
        s=max(d*u[i][j],s);}
    }
    cout<<s*3<<endl;
}

P1387 最大正方形

AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=2000;
int a[N][N],l[N][N],r[N][N],u[N][N];
int n,m,la,lb;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        char ch;
        cin>>ch;
        if(ch=='1') a[i][j]=1;
        l[i][j]=r[i][j]=j,u[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>=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<=m;j++)
    {
        if(a[i][j])
        {
            if(i>1&&a[i-1][j])
            {
              l[i][j]=max(l[i-1][j],l[i][j]);
              r[i][j]=min(r[i-1][j],r[i][j]);
              u[i][j]=u[i-1][j]+1;
            }
          la=r[i][j]-l[i][j]+1;
          la=min(la,u[i][j]);
          lb=max(lb,la);
         }
    }
    cout<<lb<<endl;
}

P2701 巨大的牛棚
AC代码

#include<bits/stdc++.h>
using namespace std;
const int N=2200;
int a[N][N],l[N][N],r[N][N],u[N][N];
int n,t,la,lb;
int main()
{
    cin>>n>>t;
    memset(a,1,sizeof a);
    for(int i=1;i<=t;i++)
    {
        int x,y;
        cin>>x>>y;
        a[x][y]=0;
    }
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    {
        l[i][j]=r[i][j]=j;
        u[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(a[i][j])
        {
            if(i>1&&a[i-1][j])
            {
              l[i][j]=max(l[i-1][j],l[i][j]);
              r[i][j]=min(r[i-1][j],r[i][j]);
              u

以上是关于动态规划之悬线法模板(最大子矩阵问题)的主要内容,如果未能解决你的问题,请参考以下文章

动态规划悬线法

BZOJ_3039_玉蟾宫_(动态规划+悬线法)

悬线法

悬线法dp

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

悬线法刷题记录