听太神的话,看了一下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; }