2019年牛客多校第三场 F(暴力+思维+模拟单调队列)
题意:找一个最大子矩阵,在这个子矩阵中最大的元素减去最小的元素,差值小于K。
题解:
A:首先,暴力遍历每一列,找到每一列的最大值,最小值。(这里不是简单的遍历)
比如有3*3的矩阵,第1行所有列(每一列的最大值,最小值),第1-2行所有列(每一列的最大值,最小值),第1-3行;第2行,第2-3行,第3行。等等以此类推记录
B:然后,开始从左到右遍历寻找那个值,左边先停在1的位置(或者的符合条件的位置),右边开始向右遍历,用一个单调队列来保存这个区间内的最大值和最小值,然后相减,若符合条件,则右边的指针继续向右遍历,当不合法的时候,左边的指针开始向右走,直至合法(用一个while来实现)。
C:单调队列,对于最大值来说,维护一个从大到小的;最小值,维护一个从小到大的。这样两个单调队列弹出的值,正好为这个区间内最大值-最小值。然后判断是否合法来让左右指针对应移动(B中)
PS:一件有趣的事情:这个题目可能是个论文题,找了十份代码基本上都是一摸一样的,我去观摩了一下咖啡鸡大佬的代码,其实核心的想法和代码结构也都是一样。本来还想要另辟蹊径,WA的我头疼。
思路基本上是这样,以下贴代码:
代码:
#include<cstdio>
#include<string>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int const ma = 1e5 + 7;
int mp[507][507], maxn[ma], minn[ma], quema[ma], quemi[ma];
int main() {
int t;
scanf("%d", &t);
while (t--) {
int n, m;
int ans = 1;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &mp[i][j]);
for (int i = 1; i <= n; i++) {
for (int p = 1; p <= n; p++) {
maxn[p] = 0, minn[p] = ma;
}
for (int k = i; k <= n; k++) {
for (int p = 1; p <= n; p++) {
maxn[p] = max(maxn[p], mp[k][p]); //找列的最大
minn[p] = min(minn[p], mp[k][p]);
}
int f1 = 1, f2 = 1, t1 = 0, t2 = 0, l = 1;
for (int r = 1; r <= n; r++) {
while (f1 <= t1 && maxn[quema[t1]] < maxn[r]) t1--; //没进入一个值后对单调队列进行排序,f1为队头,不能超过
while (f2 <= t2 && minn[quemi[t2]] > minn[r]) t2--;
quema[++t1] = r; //进入队列
quemi[++t2] = r;
while (l <= r &&((maxn[quema[f1]] - minn[quemi[f2]]) > m)) { //f2,f1的位置就是对应队列的最大和最小值
l++;
if (f1 <= t1 && quema[f1] < l)f1++; //这里就是把对应的值弹出去,判断位置来弹数据
if (f2 <= t2 && quemi[f2] < l)f2++;
}
ans = max((r - l + 1) * (k - i + 1), ans); //行*列,去找最大的矩阵
}
}
}
printf("%d\n", ans);
}
return 0;
}