把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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\)表示这个格子的最大边长):

\[(r-l+1)\times min_{p=l}^rv_{i,j,p} \]

考虑在枚举\(i,j\)的情况下,\(v_{i,j}\)完全可以看作一个序列\(S\),即原式相当于:

\[(r-l+1)\times min_{p=l}^rS_p \]

这个东西应该就非常好搞了吧。。。

写到这里突然把自己原先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;//三部曲
}
posted @ 2020-05-20 11:02  TheLostWeak  阅读(107)  评论(0编辑  收藏  举报