BZOJ 3039 玉蟾宫
2017.10.23
2017.10.23
给你一个'F'和'R'组成的矩阵,求这个矩阵的最大的只由'F'组成的子矩阵的大小。
这题可以用单调栈理解,但是同时还有一种高大上的理解方法:悬线法。
假定对于每一个点(i,j)伸出一条竖直向上的直线,这个线就是悬线。特殊定义对于一个不合法的点它的悬线长为0,如果这个点上方的点不合法也是0. 所以对于每一条悬线,我们都可以轻松推出一个东西:向上最远推出的边界(只考虑本列)。
对于每一个可能的子矩阵一定有一个限制条件——可能是某一列的列宽很矮,限制了整个子矩阵的高。但是我们在延伸的时候不需要考虑这个问题——因为对于每一个限制条件都会考虑到,不会出现一个矩阵应该走限制但是没走从而漏掉关键的子矩阵。
设$L_{i,j}$表示点(i,j)向左的最远延伸距离,$R_{i,j}$表示点(i,j)向右的最远延伸距离。首先只考虑本行的情况,很轻松推出$L_{i,j},R_{i,j}$. 而对于多行之间干扰的情况,如果(i,j)的上一行是有效的(悬线长度不为0)此时的长度就是上一行和本行应有长度的较小值.
最终答案就是$\max((L_{i,j}+R_{i,j}-1)(H_{i,j}+1))$
这里有一个东西需要特别注意:
对于一个点的L和R值,有两个值:一个是临时的“只考虑本行情况下的值”,另一个是“最终的考虑所有的值”。的确是用第一种和第二种取最大值,但是需要注意的是用第一种更新第一种,也只用第一种更新第一种。所以需要进行两次遍历,第一次求出第一种值,第二次求出第二种值。如果一次求出两个值的话就会发现有的值考虑不到延伸情况。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1010
int n,m,h[N][N],l[N][N],r[N][N],ans;
char in[2];
bool mp[N][N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
{
scanf("%s",in);
mp[i][j]=(in[0]=='F');
}
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(mp[i][j])
{
if(mp[i-1][j]) h[i][j]=h[i-1][j]+1;
l[i][j]=l[i][j-1]+1;
}
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(mp[i][j]&&mp[i-1][j]) l[i][j]=min(l[i-1][j],l[i][j]);
for(int i=1;i<=n;++i) for(int j=m;j;j--) if(mp[i][j]) r[i][j]=r[i][j+1]+1;
for(int i=1;i<=n;++i) for(int j=m;j;j--) if(mp[i][j]&&mp[i-1][j]) r[i][j]=min(r[i-1][j],r[i][j]);
/*
for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j)printf("%d ",h[i][j]);printf("\n");}
for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j)printf("%d ",l[i][j]);printf("\n");}
for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j)printf("%d ",r[i][j]);printf("\n");}
*/
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) ans=max(ans,((h[i][j]+1)*(r[i][j]+l[i][j]-1)));
printf("%d\n",ans*3);
return 0;
}