最大01子矩阵相关

听太神的话,看了一下2003王知昆的论文,受益匪浅。

貌似这种矩形的题都会跟单调队列有关系,然而我又不是非常擅长单调队列和单调栈,所以过一阵子仔细学习一下。

太神在考场上出了一道比较不错的题目(虽然不是原创),表示自己在考场上YY的非常扯淡的算法,用了一些比较奇葩的东西,如果开了O2,应该就能A了。

先上例题吧。


bzoj3039 玉蟾宫

题意:求最大的全是0的矩阵。n<=1000

这种方法叫做悬线法。

我们考虑最大的矩阵一定边界上有障碍点或者碰到了大矩形边界,我们定义悬线表示从一个点向上最长能延伸多长,那么最大的矩阵一定是一根悬线,向左和向右分别延伸到最大后的一个矩阵。

这个算法的精髓就是先求出每个点的悬线,然后处理出每个点的悬线最多能向左和向右延伸多长,最后统计一下每一个点的答案。

up[i][j]表示第i行第j列悬线的长度,l[i][j]、r[i][j]表示向左向右最长能延伸多长,那么第i行第j列的悬线向左右延伸的最大矩阵就是up[i][j]*(l[i][j]+r[i][j]+1)。

up[i][j]非常好处理,那么我们考虑怎样求l[i][j]和r[i][j]。

那么我们考虑第i行第j列,如果第i行第j-1列的悬线比它短的话,很明显不能向左延伸了,如果比它长的话,那么它就一直延伸到比它短为止,那么这是不是就相当于转化为另一个问题:一个长度为m的数列a,对于每个ai,求出一个最大区间[l,r],使al-r的最小值为ai。这个问题可以用单调栈O(n)解决,具体做法是单调增的单调栈记录坐标和权值,如果i的权值小于栈顶权值就弹出,最后答案就是最终栈顶的坐标。

说的比较麻烦,其实论文里写的比较简单,而且可以用一种无形的单调栈,然而我比较笨,觉得直接写单调栈比较简单,于是直接敲得,以下是太神的板。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 1010

using namespace std;

int up[maxn][maxn],l[maxn][maxn],r[maxn][maxn];
pair<int,int> st[maxn];
int n,m;
int a[maxn][maxn];

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		char c[5];
		for (int j=1;j<=m;j++)
		{
			scanf("%s",c);
			if (c[0]=='F') a[i][j]=0; else a[i][j]=1;
		}
	}
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=m;j++)
	    if (a[i][j]) up[i][j]=0;
	    else up[i][j]=up[i-1][j]+1;
	for (int i=1;i<=n;i++)
	{
		int top=0;
		st[++top]=make_pair(-1,0);
		for (int j=1;j<=m;j++)
		{
			while (up[i][j]<=st[top].first) top--;
			l[i][j]=j-st[top].second-1;
			st[++top]=make_pair(up[i][j],j);
		}
	}
	for (int i=1;i<=n;i++)
	{
		int top=0;
		st[++top]=make_pair(-1,m+1);
		for (int j=m;j>=1;j--)
		{
			while (up[i][j]<=st[top].first) top--;
			r[i][j]=st[top].second-j-1;
			st[++top]=make_pair(up[i][j],j);
		}
	}
	int ans=0;
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=m;j++)
	    ans=max(ans,(l[i][j]+r[i][j]+1)*up[i][j]);
	printf("%d\n",ans*3); 
	return 0;
}

bzoj1057  【ZJOI2007】棋盘制作

题意:求最大的01相间的子矩阵和子正方形。

貌似所有的01相间的问题都可以转化成全部相同的问题,或者改一改匹配条件?

如果这个格子是1,且奇偶性相同,就赋值为0,如果奇偶性不同,就赋值为1。

如果这个格子是0,且奇偶性相同,就赋值为1,如果奇偶性不同,就赋值为0。

然后就是求最大的全0或全1子矩阵和子正方形。

我们考虑一下正确性,黑白相间,则相邻的两个一定是一个奇偶性相同,一个奇偶性不同,且一个为黑,一个为白,经过赋值,则符合要求。

子正方形的话,一定是一个子矩阵的一部分,枚举一下就好了。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#define maxn 2010

using namespace std;

int up[maxn][maxn],l[maxn][maxn],r[maxn][maxn];
pair<int,int> st[maxn];
int a[maxn][maxn];
int n,m,ans1,ans2;

void work(int x)
{
	memset(up,0,sizeof(up));
	memset(l,0,sizeof(l));
	memset(r,0,sizeof(r));
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=m;j++)
	    if (a[i][j]==x) up[i][j]=0;
	    else up[i][j]=up[i-1][j]+1;
	for (int i=1;i<=n;i++)
	{
		int top=0;
		st[++top]=make_pair(-1,0);
		for (int j=1;j<=m;j++)
		{
			while (up[i][j]<=st[top].first) top--;
			l[i][j]=j-st[top].second-1;
			st[++top]=make_pair(up[i][j],j);
		}
	}
	for (int i=1;i<=n;i++)
	{
		int top=0;
		st[++top]=make_pair(-1,m+1);
		for (int j=m;j>=1;j--)
		{
			while (up[i][j]<=st[top].first) top--;
			r[i][j]=st[top].second-j-1;
			st[++top]=make_pair(up[i][j],j);
		}
	}
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=m;j++)
	  {
	  	ans1=max(ans1,(l[i][j]+r[i][j]+1)*up[i][j]);
	  	int t=min(l[i][j]+r[i][j]+1,up[i][j]);
	  	ans2=max(ans2,t*t);
	  }
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=m;j++)
	  {
	  	int x;
	  	scanf("%d",&x);
	  	if (x==0) 
	  	{
	  		if ((i&1)==(j&1)) a[i][j]=1;
	  		else a[i][j]=0;
	  	}
	  	else
	  	{
	  		if ((i&1)==(j&1)) a[i][j]=0;
	  		else a[i][j]=1;
	  	}
	  }
	work(0);
	work(1);
	printf("%d\n%d\n",ans2,ans1);
	return 0;
}

  

你可能感兴趣的:(最大01子矩阵相关)