扫描线1:朴素扫描线

扫描线1

例题1

例题1

有 n n n 根彼此相邻的柱子,宽度为 1 1 1,现给定一个长度为 n n n 的非负整数数组 A A A,其中 A [ i ] A[i] A[i] 表示第 i i i 根柱子的高度。

请问在这个柱状图中,能够找到的最大矩形面积是多少?

题解

如上图,对于这样的柱状图我们怎么去寻找最大矩形呢?

我们可以每次找出 以 a [ i ] a[i] a[i] 作为高度的最大矩形,答案必然在这些矩形之内。

不难发现以 a [ i ] a[i] a[i] 为高度的最大矩形的右边界必然是 i i i 左侧第一个小于 a [ i ] a[i] a[i] 的位置的右边一格。

同理,以 a [ i ] a[i] a[i] 为高度的最大矩形的左边界必然是 i i i 左侧第一个小于 a [ i ] a[i] a[i] 的位置的右边一格。

求上述左右边界可以用单调栈预处理,对于每个 a [ i ] a[i] a[i] 需要进行下面两个步骤:

  • 如果 a [ i ] a[i] a[i] 小于栈顶,说明栈顶的 右边界 就是 i − 1 i-1 i−1,我们不断将栈顶弹出更新它们的右边界,直到不小于栈顶。
  • 将 a [ i ] a[i] a[i] 加入栈中。

对于左边界,我们只需要 倒着进行一遍 这个过程就行。

求出每个以 a [ i ] a[i] a[i] 高的最大矩形的左右边界之后,就能统计这个矩形的面积,每次更新最大面积即可。

时间复杂度为 O ( n ) O(n) O(n)。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18

int largestRectangleArea(vector<int>& heights) {
    vector <int> minStack;
    vector <int> L(heights.size(), 0), R(heights.size(), 0);
    for (int i = 0; i < heights.size(); i++) {
        while (minStack.size() && heights[minStack.back()] > heights[i]) {
            R[minStack.back()] = i;
            minStack.pop_back();
        }
        minStack.push_back(i);
    }
    while (minStack.size()) {
        R[minStack.back()] = heights.size();
        minStack.pop_back();
    }

    for (int i = heights.size() - 1; i >= 0; i--) {
        while (minStack.size() && heights[minStack.back()] > heights[i]) {
            L[minStack.back()] = i + 2;
            minStack.pop_back();
        }
        minStack.push_back(i);
    }
    while (minStack.size()) {
        L[minStack.back()] = 1;
        minStack.pop_back();
    }

    int ans = 0;
    for (int i = 0; i < heights.size(); i++) {
        ans = max(ans, heights[i] * (R[i] - L[i] + 1));
    }

    return ans;
}

void slove () {
    int n;
    cin >> n;

    vector <int> heights(n);
    for (int i = 0; i < n; i++) {
        cin >> heights[i];
    }
    cout << largestRectangleArea(heights);
}

signed main () {
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    slove();
}

例题2

例题2

给定一个由 0 0 0 和 1 1 1 组成的 n × m n\times m n×m 矩阵,找出其中只包含 1 1 1 的最大矩阵。

数据范围: 1 ≤ n , m ≤ 200 1 \le n,m\le 200 1≤n,m≤200。

题解

01 01 01 矩阵参考上图,我们可以维护一根长度为 n n n 的扫描线数组 h e i g h t height height,从左到右开始扫描,当扫描到第 j j j 列时对于每一行 i i i 有:

  • 若 ( i , j ) (i,j) (i,j) 是 1 1 1,将 h e i g h t [ i ] height[i] height[i] 增加 1 1 1。
  • 若 ( i , j ) (i,j) (i,j) 是 0 0 0,令 h e i g h t [ i ] = 0 height[i]=0 height[i]=0。

其中 h e i g h t [ i ] height[i] height[i] 的实际意义就是,以第 j j j 列为底边,第 i i i 行能往左边延伸的最长长度。

所以要计算以 x = j x=j x=j 为底的矩阵中最大的只包含 1 1 1 的子矩阵,我们只需要计算出 h e i g h t height height 中的最大矩形面积就行了,而计算 h e i g h t height height 中的最大矩形面积过程参考例题 1 1 1。

时间复杂 O ( n m ) O(nm) O(nm)。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define int long long
#define endl '\n'
#define PII pair<int,int>
#define INF 1e18


int largestRectangleArea(vector<int>& heights) {
    vector <int> minStack;
    vector <int> L(heights.size(), 0), R(heights.size(), 0);
    for (int i = 0; i < heights.size(); i++) {
        while (minStack.size() && heights[minStack.back()] > heights[i]) {
            R[minStack.back()] = i;
            minStack.pop_back();
        }
        minStack.push_back(i);
    }
    while (minStack.size()) {
        R[minStack.back()] = heights.size();
        minStack.pop_back();
    }

    for (int i = heights.size() - 1; i >= 0; i--) {
        while (minStack.size() && heights[minStack.back()] > heights[i]) {
            L[minStack.back()] = i + 2;
            minStack.pop_back();
        }

        minStack.push_back(i);
    }
    while (minStack.size()) {
        L[minStack.back()] = 1;
        minStack.pop_back();
    }

    int ans = 0;
    for (int i = 0; i < heights.size(); i++) {
        ans = max(ans, heights[i] * (R[i] - L[i] + 1));
    }

    return ans;
}

int maximalRectangle(vector<string>& matrix) {
    if (matrix.empty()) return 0;
    int n = matrix.size(), m = matrix[0].size();

    int ans = 0;
    vector <int> heights(n, 0);
    for (int j = 0; j < m; j++) {
        for (int i = 0; i < n; i++) {
            if (matrix[i][j] == '1') heights[i] ++;
            else heights[i] = 0;
        }
        ans = max(ans, largestRectangleArea(heights));
    }

    return ans;
}

void slove () {
    vector<string> matrix;
    string s;
    while (cin >> s) {
        matrix.push_back(s);
    }
    cout << maximalRectangle(matrix) << endl;
}

signed main () {
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    slove();
}

例题 3

例题3

给定 N N N 头牛的二维坐标,牛分为两个品种,姑且可以看做 − 1 -1 −1 和 1 1 1,希望找到一个最小的矩形,使得矩形内部全是 1 1 1,且矩形内部框住的 1 1 1 最多,如果框柱的 1 1 1 一样多,那么令矩形尽量小。

输出符合条件的最小矩形的面积。

数据范围: 0 ≤ x , y ≤ 1000 0\le x,y\le 1000 0≤x,y≤1000, 1 ≤ N ≤ 500 1\le N\le 500 1≤N≤500。

题解

不难发现,如果一个矩形能作为答案,那么必然这个矩形的四条边框上具有牛 1 1 1,否则我们总能收缩一条边使得矩形的面积变小,但包围住的牛数量不变。

这题与例题 2 2 2 不同之处在于,这题还具有 空地 这种情况。

离散化

我们先将所有涉及到牛的下标离线收集起来, x x x 坐标放到 p o s X posX posX 里, y y y 坐标放到 p o s Y posY posY 里,对这两个数组进行离散化。

将实际的二维矩阵离散化,保证离散化后的二维矩阵的行数与列数分别与 p o s X posX posX 和 p o s Y posY posY 大小相等。

再对这个离散化后的二维矩阵求二维前缀和,这便于 O ( 1 ) O(1) O(1) 判断某个矩形内有多少牛 1 1 1。

扫描线

维护一个方向是 x x x 轴正方向 的扫描线数组 h h h,当扫描到 x = j x=j x=j 时有:

  • 如果 ( i , j ) (i,j) (i,j) 是牛 1 1 1 或者空地,那么 h [ i ] h[i] h[i] 加 1 1 1。
  • 如果 ( i , j ) (i,j) (i,j) 是牛 0 0 0,那么 h [ i ] h[i] h[i] 置为 0 0 0。

在这里 h [ j ] h[j] h[j] 表示从 x = i x=i x=i 这根横线上的 y = j y=j y=j 处出发,往左最多走多远。

如果 h [ j ] = 2 h[j]=2 h[j]=2,那么其实就是从 p o s X [ i ] posX[i] posX[i],走到了 p o s X [ i − h [ j ] + 1 ] posX[i-h[j]+1] posX[i−h[j]+1],在离散化二维矩阵里就是从 x = i x=i x=i 走到了 x = i − h [ j ] + 1 x=i-h[j]+1 x=i−h[j]+1。

此时对 h h h 数组求最大矩形面积,具体求法是枚举 y = j y=j y=j 这条直线矩形内的最长直线,然后分别找出最远的不小于 y = j y=j y=j 长度的上下边界 y = L , y = R y=L,y=R y=L,y=R。

此时我们就得到了一个 粗糙的矩形 ,左边界是 x = i − h [ j ] + 1 x=i-h[j]+1 x=i−h[j]+1,右边界是 x = i x=i x=i,上边界是 y = L y=L y=L,下边界是 y = R y=R y=R,此时我们就可以用二维离散前缀和来求出矩形内牛 1 1 1 的个数(必然不包括牛 − 1 -1 −1)。

之所以被称为粗糙的矩形,是因为这样的矩形的边界处可能是空地,所以我们还需要收缩左边界、上边界、下边界。

所以循环判断当前边界处是否含有 牛 1 1 1,如果不含有,那么向内收缩。

我们每次用这样的矩形来更新答案即可。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1001;

int a[N][N];
vector <int> posX, posY;
vector <vector<int>> s, cow;
// 离散化
void lsh () {
    sort(posX.begin(), posX.end());
    sort(posY.begin(), posY.end());

    posX.erase(unique(posX.begin(), posX.end()), posX.end());
    posY.erase(unique(posY.begin(), posY.end()), posY.end());
}
// 预处理二维离散数组
void prefixSum () {
    s.assign(posX.size(), vector<int>(posY.size(), 0));
    cow.assign(posX.size(), vector<int>(posY.size(), 0));

    for (int i = 0; i < posX.size(); i++) {
        for (int j = 0; j < posY.size(); j++) {
            cow[i][j] = a[posX[i]][posY[j]];
        }
    }

   for (int i = 0; i < posX.size(); i++) {
        for (int j = 0; j < posY.size(); j++) {
            s[i][j] = (i > 0 ? s[i - 1][j] : 0) +
            (j > 0 ? s[i][j - 1] : 0) - (i > 0 && j > 0 ? s[i - 1][j - 1] : 0)
            + cow[i][j];
        }
    }
}

// 求二维离散数组前缀和
int getSum (int x1, int y1, int x2, int y2) {
    return s[x2][y2] -
    (x1 > 0 ? s[x1 - 1][y2] : 0) -
    (y1 > 0 ? s[x2][y1 - 1] : 0) +
    (x1 > 0 && y1 > 0 ? s[x1 - 1][y1 - 1] : 0);
}

void solve() {
    int n;
    cin >> n;


    for (int i = 1; i <= n; i++) {
        int x, y;
        cin >> x >> y;
        char ch;
        cin >> ch;

        if (ch == 'H') a[x][y] = 1;
        else a[x][y] = -1;

        posX.push_back(x);
        posY.push_back(y);
    }

    lsh();
    prefixSum();


    int maxCow = 0;
    int minS = 0;

    vector <int> h(posY.size(), 0);
    for (int i = 0; i < posX.size(); i++) {
        // 更新扫描线
        for (int j = 0; j < posY.size(); j++) {
            if (cow[i][j] != -1) h[j] ++;
            else h[j] = 0;
        }

        // 单调栈求上下边界
        vector <int> U(posY.size()), D(posY.size());
        vector <int> minStack;
	  // 求 D
        for (int j = 0; j < posY.size(); j++) {
            while (minStack.size() && h[minStack.back()] > h[j]) {
                D[minStack.back()] = j - 1;
                minStack.pop_back();
            }
            minStack.push_back(j);
        }
        while (minStack.size()) {
            D[minStack.back()] = posY.size() - 1;
            minStack.pop_back();
        }
	// 求 U
        for (int j = posY.size() - 1; j >= 0; j--) {
            while (minStack.size() && h[minStack.back()] > h[j]) {
                U[minStack.back()] = j + 1;
                minStack.pop_back();
            }
            minStack.push_back(j);
        }
        while (minStack.size()) {
            U[minStack.back()] = 0;
            minStack.pop_back();
        }


        // 开始计算粗糙矩形面积
        for (int j = 0; j < posY.size(); j++) {
            int x1 = i - h[j] + 1, x2 = i;
		  // 收缩左边界
            while (x1 < i) {
                bool is_ok = 0;
                 // 当 x 在左边界,判断左边界是否全是空地
                for (int l = U[j]; l <= D[j]; l++) {
                    if (cow[x1][l] != 0) is_ok = 1;
                }
                if (is_ok == 0) x1 ++;
                else break;
            }
            // 收缩 上下边界
            while (getSum(x1, U[j], i, U[j]) == 0 && U[j] < j) U[j] ++;
            while (getSum(x1, D[j], i, D[j]) == 0 && D[j] > j) D[j] --;

            int H = posX[x2] - posX[x1];
            int wide = posY[D[j]] - posY[U[j]];
            int S = H * wide;

            int y1 = U[j],  y2 = D[j];
            int cows = getSum(x1, y1, x2, y2);

            if (cows > maxCow) {
                maxCow = cows;
                minS = S;
            } else if (cows == maxCow) {
                minS = min(minS, S);
            }
        }
    }
    cout << maxCow << endl << minS << endl;
}
signed main() {
    solve();
}
相关推荐
wan5555cn4 小时前
中国启用WPS格式进行国际交流:政策分析与影响评估
数据库·人工智能·笔记·深度学习·算法·wps
AndrewHZ4 小时前
【图像处理基石】图像形态学处理:从基础运算到工业级应用实践
图像处理·python·opencv·算法·计算机视觉·cv·形态学处理
仰泳的熊猫4 小时前
LeetCode:1905. 统计子岛屿
数据结构·c++·算法·leetcode
Lear4 小时前
【链表】LeetCode 206.反转链表
算法
Lear4 小时前
【链表】LeetCode 24.两两交换链表中的节点
算法
xiaoxiangwendao4 小时前
[26] 删除排序数组中的重复项
算法
THGML4 小时前
排序算法解析
数据结构·算法·排序算法
月夜的风吹雨4 小时前
【C++ string 类实战指南】:从接口用法到 OJ 解题的全方位解析
c++·接口·string·范围for·auto·力扣oj
周杰伦_Jay4 小时前
【计算机网络核心】TCP/IP模型与网页解析全流程详解
网络·网络协议·tcp/ip·计算机网络·算法·架构·1024程序员节