关于扫描线法
其实吧,扫描线法真的不难,有一点空间想象能力就够了......然而我一直没有去看。
全1矩阵
就拿模板题来讲吧,给你一个边长为N的01矩阵,叫你求最大的全1矩阵面积。
以下说明BY YZX奆佬
对于20%的数据
直接暴力,时间复杂度O(R^3*C^3 ),空间复杂度O(RC) 。
直接暴力枚举i,j,k,l i,j,k,l ,分别枚举2个点的坐标,表示一个子矩阵。然后再枚举一下这个矩阵内的每个元素,将其的和求出来,最后取一个最大值。(注意需要判断一下枚举的矩阵中不能有破损的格子)
代码就不贴了(觉得太水(wu)了(liao))。
对于40%的数据
还是暴力,但是考虑优化掉求矩阵的和,时间复杂度O(R^2*C^2 ),空间复杂度O(3*R^2*C^2) 。
用一个二维数组储存输入。
用一个二维数组储存前缀和。
用一个二维数组储存0的个数的前缀和。
首先,还是暴力枚举i,j,k,l,分别枚举2个点的坐标,表示一个组矩阵,然后再判断一下这个矩阵内是否有0(破损的格子),如果可以的话,更新ANS。
代码也就不贴了(因为本人太懒(ruo)了)。
对于100%的数据
扫描线法啦2333333。
这其实是一道模板题。
这有点难讲。
红色代表破损的格子。
先枚举一层R,表示矩阵的第一行在第几行。
第二层枚举C ,表示该行矩阵的该行的该结点左边有多少连续不为破损的格子。
然后再倒序枚举一次,求出右边有多少连续不为破损的格子。
然后再枚举一次,求出高度。
最后再枚举一次,更新ans 。
注意上面的都是同级的循环,所以复杂度为O(RC) 。
所以该矩阵的最大值为6。
怎么样,看了是不是觉得很简单呢?但其实代码实现具有一定难度也很简单
代码
//by myself
#include<bits/stdc++.h>
using namespace std;
int N;
int LIS[155][155],lef[155],h[155],rig[155],MAXX;
int main()
{
scanf("%d",&N);
for (int i=1;i<=N;i++) for (int j=1;j<=N;j++) cin>>LIS[i][j];
for (int i=1;i<=N;i++) lef[i]=1,rig[i]=N,h[i]=0;//初始化
for (int i=1;i<=N;i++)
{
int l=0,r=N+1;
for (int j=1;j<=N;j++)
{
if (LIS[i][j]==0) h[j]=0,lef[j]=1,l=j;//碰到了0的点就清空所有并更新l的值
else h[j]++,lef[j]=max(lef[j],l+1);//若没有则继续扩散,并取lef的最大值
}
for (int j=N;j>=1;j--)
{
if (LIS[i][j]==0) rig[j]=N,r=j;//同上
else rig[j]=min(rig[j],r-1),MAXX=max(MAXX,h[j]*(rig[j]-lef[j]+1));//同上但取最小值并更新MAXX=高度*(右边界-左边界+1)
}
}
cout<<MAXX<<endl;
return 0;
}
但你以为这样就是全部了吗?是的哪个出题人会考你裸模版啊(USACO 6.1 A Rectangular Barn:你说啥?),比如DAY3的T3就是一道恶心的类似扫描线法的题目。
DAY3 T3
以下为团长JHJ(大味精)分析
Step1
首先我们可以先预处理一些数组
lf[i,j]表示(i,j)此点向左最多能扩展到哪里
up[i,j]表示(i,j)此点向上最多能扩展到哪里
那么问题是怎么求呢?
以求lf数组为例
思考对于每一个点,如果它前面的点比其小,那么它肯定可以接在后面,
并且可以到达前面点所到达的点
即lf[i,j]=lf[i-1,j] (a[i-1,j]<a[i,j])
如果它前面的点不比其小,那么(i,j)此点必然不能再向前扩展,所以它也只能到达它自己了
即lf[i,j]=j
求up数组同理
Step2
那么这些数组有什么用呢?
想到求最大子矩阵,自然可以想到悬线法,但是我认为这里是不能用悬线法的
悬线法是对于每一个高度,挂下悬线,选择向左向右扩展
但对于此题,显然不知道高度到底是多少,即无法穷举完每一种情况,会遗漏答案
比如:(i,j)点既能向上扩展2格,也能3格,这时就不知道应该选取哪种情况了
毕竟我们不是双向扩展,前面的情况不会代表后面的情况。
总之,我们没有很好的办法,于是只能祭出我们的究极大法暴力了
枚举这个子矩阵的右下角,在枚举向上扩展多少,对于每一个高度,计算出能向左扩展多少,最后计算答案就行了~
值得一提的是,这样做需要很好的利用状态做出剪枝
如:向上枚举高度只能到up[i,j],向左枚举到所有高度中最大的lf[i,j],当不成立时就直接退出
这样我们就可以利用那两个数组,轻松穷举状态,计算答案了,虽然1<=n,m<=1000,但一些break操作还是可以节省不少时间的。
看完以后,我就明白怎么打了,就是先预处理再暴力求解。
然后我T了????????
然后我再看了一下团长的博客
值得一提的是,这样做需要很好的利用状态做出剪枝
代码
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
int N,M;
int LIS[1005][1005],up[1005][1005],lef[1005][1005],MAXX;
int main()
{
scanf("%d%d",&N,&M);
for (int i=1;i<=N;i++) for (int j=1;j<=M;j++) cin>>LIS[i][j];
for (int i=1;i<=M;i++) up[1][i]=1;
for (int i=1;i<=N;i++) lef[i][1]=1;
for (int i=1;i<=N;i++) for (int j=2;j<=M;j++)if (LIS[i][j]>LIS[i][j-1]) lef[i][j]=lef[i][j-1];else lef[i][j]=j;
for (int i=1;i<=M;i++) for (int j=2;j<=N;j++)if (LIS[j][i]>LIS[j-1][i]) up[j][i]=up[j-1][i];else up[j][i]=j;
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
int maxl=lef[i][j];
for(int k=i;k>=up[i][j];k--)
{
maxl=max(lef[k][j],maxl);
for(int l=j;l>=maxl;l--) if(up[i][l]>k){maxl=l+1;break;}
MAXX=max(MAXX,(j-maxl+1)*(i-k+1));
}
}
}
cout<<MAXX<<endl;
return 0;
}