【题解】P2216 [HAOI2007] 理想的正方形 [单调队列]
P2216 [HAOI2007] 理想的正方形 - 洛谷
生病了,做道简单题。
注意到数据范围, 肯定不行,转而思考
或
。
我们可以预处理出每一行 长度范围的最大最小值,线性范围求极值,容易想到单调队列。
枚举每一行,固定 i 再枚举 长度范围开始的 j,用单调队列求出最大值和最小值。
这里可以找一个函数求,因为最大值和最小值的代码差不了多少。
求出第 i 行从第 j 个开始的 n 范围最大值和最小值,
还要求出从(i, j)开始的 n * n 的方阵的最大值和最小值。
可以枚举每一列,固定 j 再枚举 长度范围开始的 i。
为什么要反着来呢?因为之前每一行的 j 我们已经处理过了。
现在要单调队列的是 n 行的同一个 j,所以要先枚举 j。
这里也可以造一个函数。
这样就可以省下 的时间复杂度,为
。
考虑定义数组:
const int N = 1e3 + 10;int mat[N][N]; // 题目给出的 a * b 矩阵
int mx[N][N]; // [i][j]:表示第 i 行从第 j 个开始的 n 范围最大值
int mn[N][N]; // [i][j]:表示第 i 行从第 j 个开始的 n 范围最小值
int mx_[N][N]; // [i][j]:从(i, j)开始的 n * n 的方阵的最大值
int mn_[N][N]; // [i][j]:从(i, j)开始的 n * n 的方阵的最小值
int q[N]; // 单调队列数组
得出 mx_ 和 mn_ 数组后,枚举开始方阵的列,枚举开始方阵的行,最大最小一减就是答案。
还有些实现的细节写代码里了:
#include <bits/stdc++.h>
using namespace std;const int N = 1e3 + 10;int mat[N][N]; // 题目给出的 a * b 矩阵
int mx[N][N]; // [i][j]:表示第 i 行从第 j 个开始的 n 范围最大值
int mn[N][N]; // [i][j]:表示第 i 行从第 j 个开始的 n 范围最小值
int mx_[N][N]; // [i][j]:从(i, j)开始的 n * n 的方阵的最大值
int mn_[N][N]; // [i][j]:从(i, j)开始的 n * n 的方阵的最小值
int q[N]; // 单调队列数组
int a, b, n;// 处理一行:t[1...b],结果存入 m[1...b - n + 1]
void get_m(int m[], int t[], int flag) { // 数组传入函数就是指针,也就是 & 引用 int l = 1, r = 0;for (int j = 1; j <= b; j++) {// 维护单调队列while (l <= r && ((t[q[r]] <= t[j] && flag) || (t[q[r]] >= t[j] && !flag))) {r--;}q[++r] = j;// 移除过期元素while (l <= r && q[l] + n - 1 < j) {l++;}// 从第 n 个位置开始记录结果if (j >= n) {m[j - n + 1] = t[q[l]];}}
}// 处理一列 j:从 mx[i][j] 或 mn[i][j] 中取 i=1..a,结果存入 m[i][j] for i=1..a-n+1
void get_m_(int m[][N], int t[][N], int flag, int j) { // 二维数组传入必须指定第二维大小int l = 1, r = 0;for (int i = 1; i <= a; i++) {while (l <= r && ((t[q[r]][j] <= t[i][j] && flag) || (t[q[r]][j] >= t[i][j] && !flag))) {r--;}q[++r] = i;while (l <= r && q[l] + n - 1 < i) {l++;}if (i >= n) {m[i - n + 1][j] = t[q[l]][j];}}
}int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> a >> b >> n;for (int i = 1; i <= a; i++) {for (int j = 1; j <= b; j++) {cin >> mat[i][j];}}// 对每一行,计算滑动窗口最大值和最小值(窗口大小 n)for (int i = 1; i <= a; i++) {get_m(mx[i], mat[i], 1); // 最大值get_m(mn[i], mat[i], 0); // 最小值}// 对每一列 j(注意:mx[i] 只有 [1..b-n+1] 有效)for (int j = 1; j <= b - n + 1; j++) {get_m_(mx_, mx, 1, j); // 列方向最大值get_m_(mn_, mn, 0, j); // 列方向最小值}int ans = 1e9;for (int i = 1; i <= a - n + 1; i++) {for (int j = 1; j <= b - n + 1; j++) {ans = min(ans, mx_[i][j] - mn_[i][j]);}}cout << ans << "\n";return 0;
}