Dotcpp  >  编程教程  >  其他算法  >  悬线法实例讲解

悬线法实例讲解

点击打开在线编译器,边学边练

先说说什么是悬线?就是一条竖线,这条竖线有初始位置和高度两个性质,可以在其上端点不超过当前位置的矩形高度的情况下左右移动。


一、概述

悬线法的适用范围是单调栈的子集。具体来说,悬线法可以应用于满足以下条件的题目:

(1)需要在扫描序列时维护单调的信息;

(2)可以使用单调栈解决;

(3)不需要在单调栈上二分。

看起来悬线法可以被替代,用处不大,但是悬线法概念比单调栈简单,更适合初学 OI 的选手理解并解决最大子矩阵等问题。


二、实例讲解

悬线法是一种比较简单的DP技巧,主要用于在O(nm)的时间内解决最大矩形/最大正方形问题,即,给出一个nXm的方格矩阵,其中某些方格是不可选的,求可选的最大矩形/正方形。

悬线法1

其实这个问题用单调栈也是可以O(nm)解决的,但悬线法是一种思路更简单的方法。我们定义“悬线”是从某一格出发,一直向上能延申的最长竖线(如下图)。

最长竖线

我们定义极大矩形是无法再向上、下、左或右拓展的矩形,很明显,每一个最大矩形,都一定是一个极大矩形(否则拓展后的矩形比它更大,它就不是最大的了)。

一个重要的事实是:每个极大矩形都一定可以由某条悬线“拓展”(即把悬线尽可能地往左、往右平移)得到。实际上,在矩形上边界上找到一个不能再向上的方格(作为极大矩形,一定存在这样的方格),把它跟矩形下边界的对应格连线,即可得到这样的悬线。

悬线法2

由于一个可选的格子对应一条悬线,所以悬线最多只有nm条。因此,我们只需要枚举最多nm2条悬线就可以枚举所有的极大矩形,最大矩形一定就在其中。

接下来是十分简单的DP环节。定义uIr分别表示从某格出发向上、左、右最多能走多少格(包括自身),则:

悬线法3

然后我们定义 L、R 分别表示从当前格向上的悬线最多能向左、向右拓展多少格,则:

悬线法4


实际上我们发现LR分别可以用同一个数组来存,可以节省很多空间。对于每一格来说,它对应的悬线左右拓展能得到的最大的矩形面积为:

最大的矩形面积

代码如下:

for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= m; ++j)
        if (A[i][j])
            l[i][j] = l[i][j - 1] + 1;for (int i = 1; i <= n; ++i)
    for (int j = m; j >= 1; --j)
        if (A[i][j])
            r[i][j] = r[i][j + 1] + 1;for (int i = 1; i <= n; ++i)
    for (int j = 1; j <= m; ++j)
        if (A[i][j]) {
            u[i][j] = u[i - 1][j] + 1;
            if (A[i - 1][j]) cmin(l[i][j], l[i - 1][j]), cmin(r[i][j], r[i - 1][j]);
            cmax(ans, (r[i][j] + l[i][j] - 1) * u[i][j]);
        }

现在我们解决了最大矩形问题,那么最大正方形呢?很简单,因为最大正方形一定是最大矩形的子正方形。所以我们只需枚举所有悬线,计算每条悬线所对应矩形的最大子正方形面积:

最大子正方形面积


本文固定URL:https://www.dotcpp.com/course/1030

算法竞赛教程
第一章 算法基础
第二章 搜索算法
第三章 排序算法
第四章 字符串相关
第五章 数学相关
第六章 动态规划
第七章 数据结构
第八章 图论
第九章 计算几何
第十章 其他算法
Dotcpp在线编译      (登录可减少运行等待时间)