【BZOJ2228】[ZJOI2011] 礼物(单调栈)
大致题意: 给定一个\(n\times m\times k\)的木块,其中有一些单位坏了。让你选出一个\(a\times a\times b\)的木块,求最大的\(4ab\)。
三部曲
这道题大概可以分成三步:初始化、求答案、旋转。
具体放在代码里就是这样子的:
Init(),Work(),Ro(),Init(),Work(),Ro(),Init(),Work();
其中旋转这一部分是比较简单的,可以直接看代码。而初始化、求答案这两步就让我们一个一个进行分析。
初始化
考虑这种三维的东西特别麻烦,肯定要想办法把它给搞成二维的。
于是我们枚举一维(设为\(z\))作为\(b\)的一维,则另两维(设为\(x,y\))就是\(a\times a\)这两维。(这也是为什么要旋转的原因)
我们枚举\(z\)的每一层,然后对于这一层上每一个格子\((i,j)\),求出以\((i,j)\)为右下角最大的正方形边长。
显然,\((i,j)\)的最大边长最多也只能取到\((i-1,j)\)和\((i,j-1)\)最大边长的较小值加\(1\),然后我们从大到小暴力判断是否能取到每一个值,能取到就直接\(break\)。这样的总复杂度可以保证是\(O(n^3)\)的。
至于如何判断,我们把\(P\)视作\(0\),\(N\)视作\(1\),则一个区域可以被选择,当且仅当区域内权值和等于区域大小(即全是\(1\)),那么直接二维前缀和搞一下就行了。
求答案
好,现在我们已经求出每一个格子作为右下角的最大边长。
于是,我们就枚举长方体右下角的\(x,y\)两维坐标\((i,j)\),然后求此时的答案。
假设我们在\(z\)这一维上选取了\([l,r]\)这一区间,那么答案就是(其中\(v\)表示这个格子的最大边长):
考虑在枚举\(i,j\)的情况下,\(v_{i,j}\)完全可以看作一个序列\(S\),即原式相当于:
这个东西应该就非常好搞了吧。。。
写到这里突然把自己原先A掉的做法Hack掉了,看来这题数据是真的水。。。
正确的做法是,我们用单调栈维护每一个关键的最小值,从而求出每一个点可以作为哪一段区间的最小值,最后更新答案。
具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 150
#define Gmax(x,y) (x<(y)&&(x=(y)))
using namespace std;
int n,m,k,ans;char s[N+5][N+5][N+5];
namespace BlockPlayer
{
char t[N+5][N+5][N+5];
I void Ro()//旋转
{
RI i,j,p,x=n;for(i=1;i<=n;++i) for(j=1;j<=m;++j) for(p=1;p<=k;++p) t[i][j][p]=s[i][j][p];
n=m,m=k,k=x;for(i=1;i<=n;++i) for(j=1;j<=m;++j) for(p=1;p<=k;++p) s[i][j][p]=t[p][i][j];
}
int c[N+5][N+5],v[N+5][N+5][N+5];
I void Init()//初始化
{
RI i,j,p,w;for(p=1;p<=k;++p) for(i=1;i<=n;++i) for(j=1;j<=m;++j)
{
c[i][j]=c[i-1][j]+c[i][j-1]-c[i-1][j-1]+(s[i][j][p]=='N'),//二维前缀和
w=min(v[i-1][j][p],v[i][j-1][p])+1;W(c[i][j]-c[i-w][j]-c[i][j-w]+c[i-w][j-w]<w*w) --w;//暴算最大边长
v[i][j][p]=w;
}
}
int S[N+5],l[N+5],r[N+5];
I void Work()//求答案
{
RI i,j,p,t,T;for(i=1;i<=n;++i) for(j=1;j<=m;++j)//枚举右下角
{
for(T=0,v[i][j][k+1]=0,p=1;p<=k+1;++p)
{
l[p]=p;W(T&&v[i][j][S[T]]>=v[i][j][p]) l[p]=l[S[T]],r[S[T--]]=p-1;S[++T]=p;
//往单调栈中加一个数,同时维护每个最小值的区间范围
}
for(p=1;p<=k;++p) Gmax(ans,4*(r[p]-l[p]+1)*v[i][j][p]);//更新答案
}
}
}using namespace BlockPlayer;
int main()
{
RI i,j;for(scanf("%d%d%d",&n,&m,&k),j=1;j<=m;++j) for(i=1;i<=n;++i) scanf("%s",s[i][j]+1);
return Init(),Work(),Ro(),Init(),Work(),Ro(),Init(),Work(),printf("%d\n",ans),0;//三部曲
}