Description
Solution
首先易得暴力DP:(f[i][j][k])表示以(i,j)为左上角的边长为k的正方形的最大值(或最小值),可得(f[i][j][k] = min{a[i][j], f[i+1][j][k-1], f[i][j+1][k-1], f[i+1][j+1][k-1]}),复杂度高达(O(abn)),数量级约为(10^9),无法通过数据。
考虑优化,由于是最值问题,即RMQ问题,由此可以想到ST表,复杂度(O(ablog n)),基本能卡过时限,但是空间限制比较悬,还需要改进(其实用滚动数组就能优化到能过)。
继续优化,考虑区间最值问题的另一种解决办法:单调队列,首先将行分别维护单调队列,求出第i行中区间([j,j+n-1])中的最值,记为(m[i][j]),然后对m中的每一列维护单调队列,求出第j列中区间([i,i+n-1])的最值,记为(f[i][j]),则(f[i][j])为以(i,j)为左上角的边长为k的正方形的最值,直接统计答案即可,时空复杂度均为(O(ab)),已经足够优秀。
Code
#include <cstdio>
#include <algorithm>
const int N = 1010;
int a[N][N], mxa[N][N], mna[N][N], mxb[N][N], mnb[N][N];
int mx[N], mxhd, mxtl, mn[N], mnhd, mntl, mxp[N], mnp[N];
int n, m, k;
int main() {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) scanf("%d", &a[i][j]);
for (int i = 1; i <= n; ++i) {
mxhd = mxtl = mnhd = mntl = 0;
for (int j = 1; j <= m; ++j) {
while (mxtl != mxhd && mx[mxtl-1] <= a[i][j]) mxtl--;
mx[mxtl] = a[i][j]; mxp[mxtl] = j; mxtl++;
if (mxp[mxhd] == j - k) mxhd++;
if (j >= k) mxa[i][j] = mx[mxhd];
while (mntl != mnhd && mn[mntl-1] >= a[i][j]) mntl--;
mn[mntl] = a[i][j]; mnp[mntl] = j; mntl++;
if (mnp[mnhd] == j - k) mnhd++;
if (j >= k) mna[i][j] = mn[mnhd];
}
}
int ans = 0x3f3f3f3f;
for (int j = k; j <= m; ++j) {
mxhd = mxtl = mnhd = mntl = 0;
for (int i = 1; i <= n; ++i) {
while (mxtl != mxhd && mx[mxtl-1] <= mxa[i][j]) mxtl--;
mx[mxtl] = mxa[i][j]; mxp[mxtl] = i; mxtl++;
if (mxp[mxhd] == i - k) mxhd++; // mark
if (i >= k) mxb[i][j] = mx[mxhd];
while (mntl != mnhd && mn[mntl-1] >= mna[i][j]) mntl--;
mn[mntl] = mna[i][j]; mnp[mntl] = i; mntl++;
if (mnp[mnhd] == i - k) mnhd++; // mark
if (i >= k) mnb[i][j] = mn[mnhd];
}
for (int i = k; i <= n; ++i) {
ans = std::min(ans, mxb[i][j] - mnb[i][j]);
}
}
printf("%d
", ans);
return 0;
}
Note
mark那里将i
错写成j
,导致调了好久,还是要注意。