题意
-
给我们一个n * m矩阵,要求我们求出一个面积最大的子矩阵,满足其内部的极差小于等于c, 同时宽度小于等于100
-
输入 m, n, c,求这个最大面积。n,m <= 700,c <= 10
思路
-
如果是暴力枚举的话,每次需要枚举子矩阵,并且扫描一遍这个子矩阵,时间复杂度是无法通过的。
-
我们考率另一种枚举方式,我们先枚举左边界l与右边界r,这样是100*m的复杂度。
-
然后我们要考虑的是在这个
[ l , r ] ∗ [ 1 , n ] [l, r] * [1, n] [l,r]∗[1,n]
的长条矩阵中拿到一块符合要求的
[ l , r ] ∗ [ b o t t o m , t o p ] [l, r] * [bottom, top] [l,r]∗[bottom,top]
然后更新答案
-
如果能在线性时间内算出这个结果, 那么就是可行的算法。我们首先可以对这个长条状的子矩阵进行预处理,对每一行都预处理出它的最大值和最小值。这个操作看似是 O ( ( r − l + 1 ) ∗ n ) O((r - l + 1) * n) O((r−l+1)∗n) 的,但是因为我们先枚举的左区间之后枚举的右区间,所以本次处理每行的最大最小值只需要将增加的有边界的一列数考虑进去就可以了,时间为 O ( n ) O(n) O(n)
-
之后这个子问题也就转化成了“一个序列中选出极差小于等于c的最长的一段子序列”。这是有关单向逐步移动的区间最值问题,所以可以使用单调队列,可以满足在线性时间内完成这个任务,求出 [ b o t t o m , t o p ] [bottom, top] [bottom,top]
-
总时间复杂度为 O(100 * n * m) (可能这也是题目中限定宽度为100的原因)
AC代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int m, n, c;
int aa[705][705];
int maxl[705], minl[705];
int quemx[705], quemn[705];
int main()
{
while (scanf("%d%d%d", &m, &n, &c) == 3)
{
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
scanf("%d", &aa[i][j]);
}
}
int ans = 0;
for (int l = 1; l <= m; ++l)
{
for (int i = 1; i <= n; ++i)
{
maxl[i] = aa[i][l];
minl[i] = aa[i][l];
}
int mxr = min(m, l + 99);
for (int r = l; r <= mxr; ++r)
{
for (int i = 1; i <= n; ++i)
{
maxl[i] = max(maxl[i], aa[i][r]);
minl[i] = min(minl[i], aa[i][r]);
}
int hx = 1;
int hn = 1;
int tx = 1;
int tn = 1;
int ll = 1;
for (int i = 1; i <= n; ++i)
{
while (hx != tx && maxl[quemx[tx - 1]] <= maxl[i])
{
--tx;
}
quemx[tx++] = i;
while (hn != tn && minl[quemn[tn - 1]] >= minl[i])
{
--tn;
}
quemn[tn++] = i;
while (hn != tn && hx != tx && maxl[quemx[hx]] - minl[quemn[hn]] > c)
{
++ll;
if (quemx[hx] < ll)
{
++hx;
}
if (quemn[hn] < ll)
{
++hn;
}
}
ans = max(ans, (i - ll + 1) * (r - l + 1));
}
}
}
printf("%d\n", ans);
}
return 0;
}