[BZOJ1057]ZJOI2007棋盘制作|DP|单调栈

这题好几天以前就看了,不过一直有一些细节想不通就没写下来。。

首先发现棋盘矩阵上横纵坐标之和的奇偶性不同的点都是相反的,所以首先把横纵坐标之和为奇(或者是偶,这都不重要)的点取反,这样任务就变成了求一个最大全01的子矩阵。。

先考虑一维的情况,h[i]表示以i为终点的最长连续0的长度,有h[i]=a[i]==0? h[i-1]+1:0,这样可以On)轻松求出。。

拓展到高维,首先同样按照一维的方法,h[i][j]表示第i行以j为终点的最长连续0的长度,预处理出h[]。。接下来考虑一列一列来更新答案,对于单独的一列i,若以h[i][j]为子矩阵的一个边长,则它能往上下扩展的最大长度len就是另一个边长,所谓扩展就是向一个方向扫描知道碰到h值比自己小位置。。

[BZOJ1057]ZJOI2007棋盘制作|DP|单调栈_第1张图片

如上图,第3行最多能向上扩展2,向下扩展1len=4。。

可是暴力去扩展是On)的,总复杂度就会变成On^3),显然不行。。于是要用一种叫做单调栈的东西。。在这里栈中元素有2个域,一是压入时这个点对应的h,二是这个点向上能扩展到的最小下标ih域在这个栈里是单调递增的。。从上往下依次将每个点压入栈中,若栈顶的h<=要压入的h,那么直接压入,否则将栈顶弹出,并计算以栈顶的h为一个边长的最大子矩阵面积。。不妨设当前要压入的点的行标为j,栈顶要弹出的点的最小下标域为i,那么显然i就是栈顶的点能向上扩展到的最大长度,又因为栈是单调的,所以[i,j-1]上所有点的h一定都<=栈顶的h(否则栈顶元素之前会被弹出),那么栈顶能向下扩展到的行标就是j-1了,所以以栈顶的h为一个边长的最大子矩阵面积就为h*i-j,正方形的话只要取一下min就行了。。这样一直将栈顶弹出并更新答案,直到栈顶的h<=要压入的h或栈为空。。

还有最关键的一步,在最后要压入一个h0的元素,以保证所有的点都能出栈。。由于所有的点都要出栈一次,而出栈的时候就会被更新,所以最优解一定会被取到。。

对于这题最大全01都要做一遍。。

还有个悬线法,我看的有点晕,就不写了。。

#include
#include
#include
#define N 2005
#define clr(a) memset(a,0,sizeof(a))
using namespace std;
struct node{
	int xu,h;
	node(){}
	node(int xu,int h):xu(xu),h(h){}
}st[N];
int n,m,i,j,ans1=0,ans2=0,top,a[N][N],h[N][N];
void push(int i,int h)
{
	int now=i;
	while (top&&st[top].h>h)
	{
		ans1=max(ans1,min(i-st[top].xu,st[top].h)*min(i-st[top].xu,st[top].h));
		ans2=max(ans2,(i-st[top].xu)*st[top].h);
		now=st[top--].xu;
	}
	st[++top]=node(now,h);
}
void cal()
{
	int i,j;
	for (i=1;i<=n;i++)
		for (j=1,h[i][0]=0;j<=m;j++)
			h[i][j]=a[i][j]? h[i][j-1]+1:0;
	for (j=1;j<=m;j++)
	{
		top=0;
		for (i=1;i<=n;i++)
			push(i,h[i][j]);
		push(n+1,0);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;i++)
		for (j=1;j<=m;j++)
		{
			scanf("%d",&a[i][j]);
			if ((i+j)%2) a[i][j]^=1;
		}
	cal();
	for (i=1;i<=n;i++)
		for (j=1;j<=m;j++)
			a[i][j]^=1;
	cal();
	printf("%d\n%d",ans1,ans2);
}


你可能感兴趣的:(BZOJ)