2019年牛客多校第三场 F(暴力+思维+模拟单调队列)

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;
}

你可能感兴趣的:(思维,单调队列)