悬线法求最大子矩阵

codevs 2491 玉蟾宫
题目描述 Description

  有一天,小猫rainbow和freda来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。

  这片土地被分成N*M个格子,每个格子里写着'R'或者'F',R代表这块土地被赐予了rainbow,F代表这块土地被赐予了freda。
  现在freda要在这里卖萌。。。它要找一块矩形土地,要求这片土地都标着'F'并且面积最大。
  但是rainbow和freda的OI水平都弱爆了,找不出这块土地,而蓝兔也想看freda卖萌(她显然是不会编程的……),所以它们决定,如果你找到的土地面积为S,它们每人给你S两银子。

输入描述 Input Description

  第一行两个整数N,M,表示矩形土地有N行M列。
  接下来N行,每行M个用空格隔开的字符'F'或'R',描述了矩形土地。

输出描述 Output Description

  输出一个整数,表示你能得到多少银子,即(3*最大'F'矩形土地面积)的值。

样例输入 Sample Input

5 6
R F F F F F
F F F F F F
R R R F F F
F F F F F F
F F F F F F

样例输出 Sample Output

45

数据范围及提示 Data Size & Hint

  对于50%的数据,1<=N,M<=200
  对于100%的数据,1<=N,M<=1000

方法一:求极大子矩阵 讲解不是很好,请参考:2003年国家集训队论文,王知昆,浅谈用极大化思想解决最大子矩形问题http://wenku.baidu.com/view/6b348c0203d8ce2f006623bb.html

这个问题可以转化为在矩阵中找一个全是F的最大子矩阵。

一个全是F的最大子矩阵可以看做由一个宽为1,高位h的矩形左右平移得到。

所以我们可以得出算法:

若a[i][j]=F,设以a[i][j]为基准点,向上可以延伸到的最大高度为h,即为矩阵的高。什么叫做最大高度?当往上延伸,到达上边界或碰上R时停下,所得到的高度。

设left[i][j]表示a[i][j]向左平移,最多平移到哪一列。什么是最多平移到哪一列?当向左找,到达左边界或这一列中有R时停下。

设right[i][j]表示a[i][j]向右平移,最多平移到哪一列。原理同上

那么以点a[i][j]扩展,所能得到的最大子矩阵为(right[i][j]-left[i][j]+1)

如何确定这三个数呢?对于每一个a[i][j]都需要挨个搜吗?不。

我们可以通过循环,把第一维i压掉,只留下第二维j。那么可以实现h[],right[],left[]在n行中递推试用,O(1)复杂度确定。

具体方法如下:

首先初始化条件:所有的left[]=1,right[]=列数,表示可以无限延伸,h[]=0,高度为0

设当前循环到第i行

设l表示本行中从到达第j个点,j向左碰到的第一个R,r表示向右碰到的第一个R

初始时l=0,r=列数+1

A.h[j]=p表示以a[i][j]为基准点,从第i行往上可以延伸到的最大高度。

1、如果a[i][j]=R,h[j]置为0,第i行之上已经使用完了h[j],而第i行及以下,因为第i行为R,阻断了下面的矩阵与上面连成一个矩阵,所以要置0。

2、如果a[i][j]=F,h[j]++,即可以与上一行连成一个矩阵,高度由上一行+1.

B.left[j]表示以a[i][j]为基准点,最多向左延伸到的列,即列数最大。

1、如果a[i][j]=R,

    ①:a[i][j]置为1,因为left只有一维且以列为下标,所以下一次再用到left[j]是下一行,也就是a[i+1][j],那么由于第i行的阻断,第i+1行不能与前面连成一个矩阵,所以置为最初条件。

     ②:更新l=j。

2、如果a[i][j]=F,left[j]=max(left[j],l+1)。因为left[i]在更新之前还是第i-1行的结果,所以要从第i-1行和第i行中取大。l+1是因为l存的是R的位置,矩形延伸不能到有R的地方

C.right[j]表示以a[i][j]为基准点,最多向右延伸的列,即列数最小。

1、那么如果a[i][j]=R,

     ①:a[i][j]置为m,因为right只有一维且以列为下标,所以下一次再用到right[j]是下一行,也就是a[i+1][j],那么由于第i行的阻断,第i+1行不能与前面连成一个矩阵,所以置为最初条件。

     ②:更新r=j。

2、如果a[i][j]=F,right[j]=min(right[j],r-1)。因为right[i]在更新之前还是第i-1行的结果,所以要从第i-1行和第i行中取小。r-1是因为r存的是R的位置,矩形延伸不能到有R的地方

D.更新ans

#include<cstdio>
#include<iostream> using namespace std; int n,m,ans; char x; int a[1001][1001],h[1001],leftt[1001],rightt[1001]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { cin>>x; if(x=='R') a[i][j]=1;//1代表有障碍,不能延伸了 } for(int j=1;j<=m;j++)//初始化 { leftt[j]=1; rightt[j]=m; } for(int i=1;i<=n;i++)//枚举每一行 { int l=0,r=m+1;//l向左第一个碰到的障碍,r向右第一个碰到的障碍 for(int j=1;j<=m;j++)//从左往右枚举每一列,确定l { if(a[i][j])//当前点是障碍 { l=j;//更新向左碰到的第一个障碍 h[j]=0;//自本行以下子矩阵高度重新计数 leftt[j]=1;//因为left只有一维且以列为下标,所以下一次再用到left[j]是下一行,也就是a[i+1][j],那么由于第i行的阻断,第i+1行不能与前面连成一个矩阵,所以置为最初条件 } else//当前点不是障碍,可以参与组成矩阵 { h[j]++;//当前列高度+1 leftt[j]=max(leftt[j],l+1);//left[i]在更新之前还是第i-1行的结果,所以要从第i-1行和第i行中取大。l+1是因为l存的是R的位置,矩形延伸不能到有障碍的地方 } } for(int j=m;j>=1;j--)//同上更新r,确定向右找到的第一个R { if(a[i][j]) { r=j; rightt[j]=m; } else rightt[j]=min(rightt[j],r-1); ans=max(ans,(rightt[j]-leftt[j]+1)*h[j]); } } printf("%d",ans*3); }

 

 

 

posted @ 2016-12-24 15:26  TRTTG  阅读(486)  评论(0编辑  收藏  举报