【二维单调队列】AcWing 4964. 子矩阵
前言
二维单调队列简介
二维单调队列是一维单调队列在二维矩阵上的扩展,适用于二维滑动窗口的最值问题,可以在 \(O(m \times n)\) 的时间复杂度内求出矩阵中每个固定大小子矩阵的最值。
二维单调队列的基本思想
二维单调队列的实现通常分为两步:
- 行方向的一维单调队列:对每一行使用单调队列,求出每个窗口的列方向最值
- 列方向的一维单调队列:对第一步的结果,在列方向上再次使用单调队列
题目
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;
}
浙公网安备 33010602011771号