1. 程式人生 > >最大01子矩陣相關

最大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;
}