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