[单调队列] 理想的正方形 题解

题目描述

有一个a * b的整数组成的矩阵,现请你从中找出一个n * n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。

输入格式

第一行为3个整数,分别表示a,b,n的值第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每 行相邻两数之间用一空格分隔。 100%的数据2<=a,b<=1000,n<=a,n<=b,n<=100

输出格式

仅一个整数,为a * b矩阵中所有“n * n正方形区域中的最大整数和最小整数的差值”的最小值。

样例

样例输入

5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2

样例输出

1

题解

拿到本题,我们很容易想到遍历a * b矩阵中每个n * n的子矩阵并分别找出极差,最后输出最小的那个;

但很明显会TLE,于是需要换思路;

我们发现,TLE的根本在于循环次数太多,为什么循环次数会多呢?因为我们是基于二维来处理的这个问题,倘若将此问题转化成一个或几个一维的问题,就可以得到正解;

考虑每个n * n的子矩阵,我们发现,找最大值必须满足在这n行和n列都最大,最小也一样;

如何实现在这n行和n列都最大呢? 要在一维的前提下实现它,我们可以先求行再求列,先预处理出一个数组ma[i][j]代表第i行前j个元素中的最大值,mi[i][j]代表第i行前j个元素中的最小值,再以这个ma数组为基准找每一列的最大(最小)值,这样就可以做到在每个n * n的子矩阵的右下角存储此矩阵的最大值和最小值了;

为什么此做法不会TLE呢,因为

1.他是在一维基础上实现的;

2.找最大(最小)值必须满足在这n行和n列都最大(最小),所以如果某个数在一行都不是最大(最小)值的话,那么它也不是这个n * n的矩阵的最大(最小)值,上述思路优化掉了这些数,所以不会TLE;

对于找最大(最小)值,如果遍历又会超时,这里采用单调队列优化,找最小值就维护一个单调递增队列,找最大值就维护一个单调递减序列,每次记录队首元素即可;

代码

#include <iostream>
#include <deque>
using namespace std;
int n, m, b;
int a[1005][1005];
deque<int> dmi;
deque<int> dma;
long long ma[1005][1005];
long long mi[1005][1005];
long long ans;
int main() {
	cin >> n >> m >> b;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			cin >> a[i][j];
		}
	}
	ans = 0x3f3f3f3f;
	for (int i = 1; i <= n; i++) {
		dmi.clear();
		dma.clear();
		for (int j = 1; j <= m; j++) {
			while(!dmi.empty() && a[i][dmi.back()] >= a[i][j]) {
				dmi.pop_back();
			}
			dmi.push_back(j);
			while(!dmi.empty() && dmi.front() <= j - b) dmi.pop_front();
			mi[i][j] = a[i][dmi.front()];
			while(!dma.empty() && a[i][dma.back()] <= a[i][j]) {
				dma.pop_back();
			}
			dma.push_back(j);
			while(!dma.empty() && dma.front() <= j - b) dma.pop_front(); //注意这一行和下一行的先后顺序;
			ma[i][j] = a[i][dma.front()];
		}
	}
	for (int j = 1; j <= m; j++) {
		dmi.clear();
		dma.clear();
		for (int i = 1; i <= n; i++) {
			while(!dmi.empty() && mi[dmi.back()][j] >= mi[i][j]) {
				dmi.pop_back();
			}
			dmi.push_back(i);
			while(!dmi.empty() && dmi.front() <= i - b) dmi.pop_front();
			while(!dma.empty() && ma[dma.back()][j] <= ma[i][j]) { //从横向更新的基础上纵向更新;
				dma.pop_back();
			}
			dma.push_back(i);
			while(!dma.empty() && dma.front() <= i - b) dma.pop_front();
			if (i >= b && j >= b) ans = min(ans, ma[dma.front()][j] - mi[dmi.front()][j]); //只有能构成一个子矩阵时才能找ans;
		}
	}
	cout << ans;
	return 0;
}

后记:这个题还有一种模拟退火做法,这里不再细讲;

posted @ 2024-02-22 08:12  Peppa_Even_Pig  阅读(15)  评论(0编辑  收藏  举报