• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【二维单调队列】AcWing 4964. 子矩阵

前言

二维单调队列简介

二维单调队列是一维单调队列在二维矩阵上的扩展,适用于二维滑动窗口的最值问题,可以在 \(O(m \times n)\) 的时间复杂度内求出矩阵中每个固定大小子矩阵的最值。

二维单调队列的基本思想

二维单调队列的实现通常分为两步:

  1. 行方向的一维单调队列:对每一行使用单调队列,求出每个窗口的列方向最值
  2. 列方向的一维单调队列:对第一步的结果,在列方向上再次使用单调队列

题目

https://www.acwing.com/problem/content/description/4967/

题解

二维单调队列模板题,使用两个二维单调队列分别维护子矩阵的最大值和最小值,并按照题目求乘积之和即可。

参考代码

#include<bits/stdc++.h>
#define PII pair<int, int>
using namespace std;

typedef long long ll;
constexpr int MOD1 = 1e9 + 7;
constexpr int MOD2 = 998244353;
constexpr int N = 1e3 + 7;
int T = 1, n, m, r, c;
int a[N][N];
int mq1[N], mq2[N];
PII p[N][N];

int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    ll ans = 0LL;
    cin >> n >> m >> r >> c;
    for (int i = 0; i < n; ++ i) {
        int f1 = 0, r1 = -1, f2 = 0, r2 = -1;
        for (int j = 0; j < m; ++ j) {
            cin >> a[i][j];

            while (r1 >= f1 && a[i][j] >= a[i][mq1[r1]]) -- r1;
            while (r1 >= f1 && j - mq1[f1] + 1 > c) ++ f1;
            mq1[++ r1] = j;

            while (r2 >= f2 && a[i][j] <= a[i][mq2[r2]]) -- r2;
            while (r2 >= f2 && j - mq2[f2] + 1 > c) ++ f2;
            mq2[++ r2] = j;

            p[i][j] = {a[i][mq1[f1]], a[i][mq2[f2]]};
        }
    }

    for (int i = c - 1; i < m; ++ i) {
        int f1 = 0, r1 = -1, f2 = 0, r2 = -1;
        for (int j = 0; j < n; ++ j) {
            PII &pr = p[j][i];
            while (r1 >= f1 && pr.first >= p[mq1[r1]][i].first) -- r1;
            while (r1 >= f1 && j - mq1[f1] + 1 > r) ++ f1;
            mq1[++ r1] = j;

            while (r2 >= f2 && pr.second <= p[mq2[r2]][i].second) -- r2;
            while (r2 >= f2 && j - mq2[f2] + 1 > r) ++ f2;
            mq2[++ r2] = j;

            if (j >= r - 1) ans = (ans + 1LL * p[mq1[f1]][i].first * p[mq2[f2]][i].second) % MOD2;
        }
    }

    cout << ans << '\n';
    return 0;
}

更通用地,将其封装为通用模板类(性能表现会比较差):

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
constexpr int MOD1 = 1e9 + 7;
constexpr int MOD2 = 998244353;
constexpr int N = 1e3 + 7;
int T = 1, n, m, r, c;
int a[N][N];

/*二维单调队列*/
template<typename T>
class MonoQueueTwoDimension {
private:
    T grid;
    int n, m, r, c;
    // cpp 20 及以上可以用 std::remove_cvref_t 进行类型萃取
    // using ElementType = typename std::remove_cvref_t<decltype(grid[0][0])>;
    using ElementType = typename std::remove_reference_t<decltype(grid[0][0])>;
    using PEE = pair<ElementType, ElementType>;
    vector<vector<PEE>> p, res;
    ElementType mq1[N], mq2[N];

    // 行方向的一维单调队列:对每一行使用单调队列,求出每个窗口的列方向最值
    void handleRows() {
        for (int i = 0; i < n; ++ i) {
            int f1 = 0, r1 = -1, f2 = 0, r2 = -1;
            for (int j = 0; j < m; ++ j) {
                // 维护最大值
                while (r1 >= f1 && grid[i][j] >= grid[i][mq1[r1]]) -- r1;
                while (r1 >= f1 && j - mq1[f1] + 1 > c) ++ f1;
                mq1[++ r1] = j;
                // 维护最小值
                while (r2 >= f2 && grid[i][j] <= grid[i][mq2[r2]]) -- r2;
                while (r2 >= f2 && j - mq2[f2] + 1 > c) ++ f2;
                mq2[++ r2] = j;
                // first 维护最大值,second 维护最小值
                p[i][j] = {grid[i][mq1[f1]], grid[i][mq2[f2]]};
            }
        }
    }

    // 列方向的一维单调队列:对第一步的结果,在列方向上再次使用单调队列
    void handleColumns() {
        for (int i = c - 1; i < m; ++ i) {
            int f1 = 0, r1 = -1, f2 = 0, r2 = -1;
            for (int j = 0; j < n; ++ j) {
                PEE &pr = p[j][i];
                // 维护最大值
                while (r1 >= f1 && pr.first >= p[mq1[r1]][i].first) -- r1;
                while (r1 >= f1 && j - mq1[f1] + 1 > r) ++ f1;
                mq1[++ r1] = j;
                // 维护最小值
                while (r2 >= f2 && pr.second <= p[mq2[r2]][i].second) -- r2;
                while (r2 >= f2 && j - mq2[f2] + 1 > r) ++ f2;
                mq2[++ r2] = j;
                // first 维护最大值,second 维护最小值
                res[j][i] = {p[mq1[f1]][i].first, p[mq2[f2]][i].second};
            }
        }
    }

public:
    MonoQueueTwoDimension(T grid, int row, int column): grid(grid), n(row), m(column) {
        p = vector(n, vector<PEE>(m));
        res.assign(n, vector<PEE>(m));
    }

    MonoQueueTwoDimension(T grid, int row, int column, int subGridRow, int subGridColumn): grid(grid), n(row), m(column), r(subGridRow), c(subGridColumn) {
        p = vector(n, vector<PEE>(m));
        res.assign(n, vector<PEE>(m));
        handle(subGridRow, subGridColumn);
    }

    ElementType getMax(int x, int y) {// 获取右下角坐标为 (x, y) 的子矩阵的最大元素
        return res[x][y].first;
    }

    ElementType getMin(int x, int y) {// 获取右下角坐标为 (x, y) 的子矩阵的最小元素
        return res[x][y].second;
    }

    bool handle(int subGridRow, int subGridColumn) {// 维护出二维单调队列
        r = subGridRow, c = subGridColumn;
        if (r > n || c > m) return false;// 不存在大小为 r × c 的子矩阵
        handleRows();// 第一步:维护行方向上的一维单调队列
        handleColumns();// 第二步:在第一步的基础上,维护列方向上的二维单调队列
        return true;// 二维单调队列维护完成
    }
};

int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    ll ans = 0LL;
    cin >> n >> m >> r >> c;
    for (int i = 0; i < n; ++ i) {
        for (int j = 0; j < m; ++ j) {
            cin >> a[i][j];
        }
    }
    MonoQueueTwoDimension mq(a, n, m, r, c);
    for (int i = r - 1; i < n; ++ i) {
        for (int j = c - 1; j < m; ++ j) {
            ans = (ans + 1LL * mq.getMax(i, j) * mq.getMin(i, j)) % MOD2;
        }
    }
    cout << ans << '\n';
    return 0;
}

posted on 2026-01-11 16:48  RomanLin  阅读(12)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3