扫描线法

关于扫描线法

其实吧,扫描线法真的不难,有一点空间想象能力就够了......然而我一直没有去看。

全1矩阵

就拿模板题来讲吧,给你一个边长为N的01矩阵,叫你求最大的全1矩阵面积。

以下说明BY YZX奆佬

对于20%的数据

直接暴力,时间复杂度O(R^3*C^),空间复杂度O(RC) 。

直接暴力枚举i,j,k,i,j,k,l ,分别枚举2个点的坐标,表示一个子矩阵。然后再枚举一下这个矩阵内的每个元素,将其的和求出来,最后取一个最大值。(注意需要判断一下枚举的矩阵中不能有破损的格子)

代码就不贴了(觉得太水(wu)了(liao))。

对于40%的数据

还是暴力,但是考虑优化掉求矩阵的和,时间复杂度O(R^2*C^),空间复杂度O(3*R^2*C^2) 。

用一个二维数组储存输入。

用一个二维数组储存前缀和。

用一个二维数组储存0的个数的前缀和。

首先,还是暴力枚举i,j,k,l,分别枚举2个点的坐标,表示一个组矩阵,然后再判断一下这个矩阵内是否有0(破损的格子),如果可以的话,更新ANS。

代码也就不贴了(因为本人太懒(ruo)了)。

对于100%的数据

扫描线法啦2333333。

这其实是一道模板题。

这有点难讲。

红色代表破损的格子。

alt

先枚举一层R,表示矩阵的第一行在第几行。

第二层枚举C ,表示该行矩阵的该行的该结点左边有多少连续不为破损的格子。

然后再倒序枚举一次,求出右边有多少连续不为破损的格子。

然后再枚举一次,求出高度。

最后再枚举一次,更新ans 。

注意上面的都是同级的循环,所以复杂度为O(RC) 。

所以该矩阵的最大值为6。

alt

怎么样,看了是不是觉得很简单呢?但其实代码实现具有一定难度也很简单

代码
//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;
}

  

转载于:https://www.cnblogs.com/GREED-VI/p/9887399.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值