洛谷好题 P1950 长方形 详细思路题解 有图 附代码

文章讨论了一种优化枚举策略来解决编程问题,特别是关于在给定的矩阵中找出符合条件的长方形数量的问题。通过避免重复和遗漏,作者提出了从上到下、从左到右的枚举方法,并利用单调栈计算每个格子的扩展范围,确保了正确计算所有可能的长方形。这种方法涉及到动态规划思想和数据结构的巧妙应用。
摘要由CSDN通过智能技术生成

在我的洛谷博客阅读

原题传送门(洛谷 P1950 长方形)

暴力做法

直接枚举矩形四条边的所有可能情况,判断每种情况是否符合要求(即长方形内部是否不含画过的方块),但显然这样枚举的情况数非常多,效率太低。

尝试优化思路

把所有剪出来的长方形情况总数划分为划分为互不重复、容易计数的小范围情况,再把所有小范围的情况总和加起来得出答案,其实就是dp的思想。

如何设计枚举方式呢?

我们需要找一个标准去枚举所有情况,而在此题中最容易想到的标准就是每个没有被画过的格子——因为所有符合要求的长方形一定是全由没有被画过的格子组成的,故我们可以通过枚举每一个格子能“扩展”形成的长方形(即这个格子被长方形包含)个数来计算总数。

但是,简单思考一下就能发现,直接按照每个格子枚举所有可扩展形成的长方形数目必然会出现重复,因为一个长方形可以被其中每一个格子扩展得到,而重复的情况数也不好直接计算。

设计不重不漏的枚举

因为每个长方形一定全部包含没画过的格子,一定能被没画过的格子扩展得到,枚举每个没画过的格子得到的情况中一定能包含所有长方形的情况,所以我们已经保证了不重复,那么如何保证不重复呢?

下面以下图这个例子来说明(红色表示涂过的格子)

对于每个白色格子,显然我们可以先从上到下枚举每一行,再从左到右枚举这一行的每个格子。

在竖直方向上枚举并避免重复

枚举到任意一行时,对于每个格子,我们可以发现,这个格子向上能到达的第一个红色格子会限制其继续向上枚举长方形,因为再向上形成的长方形必定包含这个红色格子,不满足要求,所以枚举的每一行,我们只考虑这一行上的每个格子被上面红色格子限制的高度这一范围内的情况,再上方的格子不需要考虑,同样地,我们也不考虑当前行向下扩展的长方形,每个长方形只能被底下的格子枚举到,这样就避免了在竖直方向上的重复。

举个例子,枚举到上面例子中的第四行时,只需要关心下图中这些蓝色方块:

同时,对于每一行,记录每个格子被上面红色格子限制的高度 h h h ,后面的计算会用到。

在水平方向上枚举并避免重复


以下说明中 ( a , b ) (a,b) (a,b) 表示第 a a a 行第 b b b


同上,以枚举到第四行为例。如果直接枚举第二个格子,则它的扩展除了会受到自身高度的限制,还会受到它的左边——第一个格子,和右边——第三个格子的限制高度 h h h 限制,即第二个格子左侧不能向高度为0的第一个格子的限制即格子 ( 4 , 1 ) (4,1) (4,1) 扩展, 右侧不能向高度为1的第三个格子的限制 即格子 ( 3 , 3 ) (3,3) (3,3) 扩展。下图中阴影部分即为第二个格子能扩展到的范围(即能扩展到9个不同长方形):

而出现重复的情况在于,不同的格子扩展到了相同的长方形。例如,第二个格子会向右扩展到第三个格子,枚举到这个由格子 ( 2 , 4 ) (2,4) (2,4) 和格子 ( 3 , 4 ) (3,4) (3,4) 组成的长方形,而到了第三个格子向左扩展时,也会扩展到第二个格子,枚举了相同的长方形。

如何避免这样的重复呢?由于重复情况是左边向右扩展和右边向左扩展发生重叠造成的,所以我们可以限定每个格子能向左和向右扩展的范围来保证其不发生重叠。

如何限定扩展的范围?我们的目标是让重复的长方形有且仅有一个格子能扩展到,而排除其他能扩展到该长方形的情况。由前面的分析,格子两侧第一个比该格子的 h h h 小的格子高度会限制该格子对应的长方形范围,所以我们只需要限制每一个长方形是被左边还是右边第一个 h h h 小的格子枚举到即可。

反过来,对于每一个格子,我们找到其左右两边第一个 h h h 小的格子的编号,分别存入数组 l l l r r r , 枚举时,根据每一个格子的 l l l r r r 规定该格子能向左和向右扩展到的长方形最大宽度,每个格子 i i i 能扩展到的长方形边界就被限制在 ( l [ i ] , r [ i ] ) (l[i],r[i]) (l[i],r[i]) 范围了,但显然,这样无法扩展到 l [ i ] l[i] l[i] r [ i ] r[i] r[i] 对应的格子本身,出现了遗漏,解决方法很简单,让任意一边计算 h

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值