P2216 [HAOI2007] 理想的正方形(单调队列)

本文介绍了一种算法,如何在给定的大整数矩阵中,通过两次单调队列操作找到n*n正方形区域的最大值和最小值,以求得两者差值的最小值。解题思路涉及使用单调队列求区间极值并更新结果矩阵。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

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

输入格式

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

输出格式

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

样例

输入 #1 复制
5 4 2
1 2 5 6
0 17 16 0
16 17 2 1
2 10 2 1
1 2 2 2
输出 #1 复制
1

说明/提示

问题规模
(1)矩阵中的所有数都不超过1,000,000,000
(2)20%的数据2<=a,b<=100,n<=a,n<=b,n<=10
(3)100%的数据2<=a,b<=1000,n<=a,n<=b,n<=100

题目分析

这道题看似比较复杂,要求出每个n * n的二维区间的最大值和最小值。我们可以通过求两次单调队列的方法获得答案:
x[i][j] //表示第i行中j到j+n-1区间中的最小值,X[i][j] //表示第i行中j到j+n-1区间中的最大值。这样我们可以用单调队列在O(a * b)的复杂度内求出这两个数组。

然后在x[][]和X[][]上求出y[i][j] //表示第j列中i到i+n-1区间中的最小值,Y[i][j] //表示第j列中i到i+n-1区间中的最大值这样求出的y[i][j],就表示矩阵[i,i+n-1][j,j+n-1]中的最小值了(Y[][]同理)。

代码如下
#include <iostream>
#include <cmath>
#include <cstdio>
#include <set>
#include <string>
#include <cstring>
#include <map>
#include <algorithm>
#include <stack>
#include <queue>
#include <bitset>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define PLL pair<LL,LL>
#define PDD pair<double,double>
#define x first
#define y second
using namespace std;
const int N=1e3+5,INF=1e9;
int m[N][N],q[N],Q[N];
int x[N][N],X[N][N];
int y[N][N],Y[N][N]; 
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false); 
	int n,a,b;
	cin>>a>>b>>n;
	for(int i=1;i<=a;i++)
		for(int j=1;j<=b;j++)
			cin>>m[i][j];
	
	for(int i=1;i<=a;i++)
	{
		int h,t,H,T;
		h=t=H=T=q[1]=Q[1]=1;			//初始化
		for(int j=2;j<=b;j++)			//单调队列求出最大值和最小值
		{
			while(h<=t&&m[i][q[t]]>=m[i][j]) t--;
			while(H<=T&&m[i][Q[T]]<=m[i][j]) T--;
			q[++t]=j,Q[++T]=j;
			if(j-q[h]>=n) h++;
			if(j-Q[H]>=n) H++;
			if(j>=n) x[i][j-n+1]=m[i][q[h]],X[i][j-n+1]=m[i][Q[H]];		//将答案记录到x[][]和X[][]中
		}
	}
	for(int j=1;j<=b-n+1;j++)
	{
		int h,t,H,T;
		h=t=H=T=q[1]=Q[1]=1;			//初始化
		for(int i=2;i<=a;i++)			//在x[][]的基础上,用单调队列按列求出最小值数组y[][](Y同理)
		{
			while(h<=t&&x[q[t]][j]>=x[i][j]) t--;
			while(H<=T&&X[Q[T]][j]<=X[i][j]) T--;
			q[++t]=i,Q[++T]=i;
			if(i-q[h]>=n) h++;
			if(i-Q[H]>=n) H++;
			if(i>=n) y[i-n+1][j]=x[q[h]][j],Y[i-n+1][j]=X[Q[H]][j];
		}
	}
	int ans=INF;
	for(int i=1;i<=a-n+1;i++)				//枚举每一个矩阵最值,求出差值的最小值
		for(int j=1;j<=b-n+1;j++)
			ans=min(ans,Y[i][j]-y[i][j]);
			
	cout<<ans<<endl; 
    return 0;
}

<think>嗯,这道题看起来是要找一个n×n的正方形区域,使得该区域内最大值和最小值的差最小。那我得仔细看看题目要求。题目给了一个a×b的矩阵,要从中找出所有可能的n×n正方形,然后计算每个这样的正方形的max-min,最后取所有中的最小值。 首先,我得想到怎么高效地计算每个n×n窗口的最大和最小值。因为直接遍历每个窗口然后找最大和最小的话,时间复杂度会很高。比如a和b都是1000的话,n是100的话,这样的窗口数量大概是(1000-100+1)^2 = 901*901≈81万,每个窗口需要100*100=1万次比较,总的时间复杂度是81万*1万=81亿次操作,这肯定会超时。 所以,必须得找一个更高效的方法。这时候,可能想到用单调队列单调队列在滑动窗口问题中常用,可以快速维护窗口内的最大值或最小值。那如何将二维的问题转化为一维的问题呢? 通常的做法是先对每一行用单调队列处理,得到每个位置向右n个元素的最大和最小值,这样每个行的每个窗口的横向极值就处理好了。然后,再对列进行处理,将纵向的n个行的极值再次用单调队列处理,得到最终的二维窗口的极值。 具体来说,比如对于每一行,我们可以预处理出每个位置j的向右n长度的窗口的最大值和最小值,保存下来。这样,对于每个行来说,处理完后,每个位置的横向窗口极值被记录下来。然后,对于每一列,我们处理这些极值数组,纵向的n长度的窗口的最大和最小值。这样,每个二维窗口的极值就是纵向处理后的结果。这样就能得到每个n×n窗口的max和min,然后计算差值,取最小即可。 那具体怎么实现呢?比如,假设原矩阵是g[a][b],首先对每一行,预处理出每行的滑动窗口最大和最小。这一步可以用单调队列。比如,对于行i,得到两个数组rmax[i][j]和rmin[i][j],表示在i行中,以j为左端点(或者右端点,这要看窗口的方向)的n长度的窗口的最大和最小值。这里需要注意的是,当处理行的时候,每个窗口的长度是n,所以每个行的处理后的数组长度应该是b - n + 1吗?或者说,每个位置j在行i中的横向窗口的最大值会被存储到rmax[i][j],其中j的范围可能从0到b-n的位置? 假设原矩阵的行数是a,列是b,n是窗口边长。对于每一行来说,处理后的横向窗口的列数是b - n +1。例如,行有b个元素,每个窗口起始位置是0到b-n的位置,所以总共有b-n+1个窗口。因此,rmax和rmin的行数还是a,但列数是b-n+1吗? 或者,可能更简单的处理方式是,每个位置的横向窗口的最大值被保存到同一行中的相应位置。比如,对于原矩阵中的每个元素g[i][j],如果在该行中,以j为右端点的窗口的最大值被保存到rmax[i][j]。那么,当处理完所有行之后,每个行的每个位置j的rmax[i][j]是包含该位置在内向左n个元素的最大值?或者向右? 这里需要明确窗口的移动方向。例如,对于每一行来说,窗口是向右移动的。所以,当处理每一行的时候,我们使用单调队列维护当前窗口的最大值。窗口的右端是当前元素的位置j。当窗口的大小超过n时,就需要将队首的元素弹出。这样,对于每个j,窗口的左端是j -n +1的位置。例如,当j >=n-1时,窗口的有效位置是[j-n+1, j],所以对于每个i,处理到j的位置时,当j >=n-1时,可以记录该窗口的最大值到rmax[i][j -n +1]的位置? 或者,可能更直接的方式是,对于每一行i,预处理出每一列j的窗口(从j到j+n-1)的最大值。但是这样的话,当j +n -1超过b-1时,就无法处理。所以正确的做法是,对每一行i,处理每个可能的窗口的起始列j,然后得到该窗口的最大和最小值,保存在rmax[i][j]和rmin[i][j]中。其中j的范围是0 <=j <=b-n。这时候,对于每一行来说,处理后的列数变为b-n+1。 这可能比较复杂。那如何用单调队列处理每一行呢?例如,假设当前处理行i,那么对于该行的每个元素,维护一个单调递减队列,当窗口滑动时,队列中的元素是可能的最大值的候选。这样,当窗口右移时,队列中保存的是当前窗口中的元素的下标,且对应的值是递减的。这样,每个窗口的最大值就是队首元素的值。同样的,最小值可以用单调递增队列来处理。 那么,对每一行处理之后,得到的rmax和rmin数组的列数是b -n +1。例如,原矩阵的列数是b,处理后每个行i对应的列数是b -n +1。然后,接下来处理列的时候,对于每一列j,在rmax数组的这一列j上,处理每一行的窗口,得到每个位置i的纵向窗口的最大值。这样,最终每个n×n窗口的最大值就是两次处理后的结果。 例如,假设原矩阵是a行b列,n是窗口的边长。首先,对每一行进行横向处理,得到rmax的每个行的列数是b-n+1。所以,处理后的rmax数组是a行,(b-n+1)列。然后,对rmax的每一列进行处理,纵向处理长度为n的窗口,得到每个位置的最大值。这样,最终的二维窗口的最大值数组的行数是a-n+1,列数是b-n+1。同理,最小值数组也是如此处理。然后,遍历所有这样的位置,计算max - min,找出其中的最小值。 所以,整个过程的时间复杂度应该是O(a*b)(每一行的处理是O(b),共a行;然后对每一列的处理是O(a) * (b-n+1)列,所以总的时间复杂度是O(ab + a(b-n+1)) )=O(ab)。对于a和b都是1000的话,这样的复杂度是可以接受的。 那现在的问题是如何具体实现这个过程。例如,在C++中,如何处理这些二维数组? 可能的实现步骤: 1. 输入a, b, n,然后读入a行,每行b个数,保存到g数组中。 2. 预处理每一行的横向窗口的最大和最小值,得到rmax和rmin数组。这两个数组的行数是a,列数是b -n +1。 3. 预处理每一列的纵向窗口的最大和最小值。对于每一列j,在rmax数组中,这一列的所有行的数据形成一个列向量,对其进行纵向窗口处理,得到最终的每个二维窗口的最大值。同样的,对rmin进行处理,得到每个二维窗口的最小值。 4. 遍历所有可能的二维窗口的位置,计算max - min,找到最小的那个差值。 具体来说,如何对每一行处理横向窗口的最大和最小值? 例如,对于一行来说,假设该行的元素是row[0...b-1]。我们需要找到每个起始位置为j的窗口的最大值,其中窗口长度为n,j的范围是0 <=j <=b-n。 这可以通过单调队列来实现。例如,维护一个双端队列deque,保存的是当前窗口内的元素的下标,且对应的元素值单调递减。当处理到元素i时: - 如果队列中的队首元素的下标小于等于i -n,则弹出队首(因为窗口已经移动,这些元素已经不在当前窗口内)。 - 然后,将当前元素i与队列尾部元素比较,如果当前元素大于等于尾部元素对应的值,则弹出尾部,直到队列为空或者遇到比当前元素大的元素。然后将i加入队列尾部。 - 当i >=n-1时,窗口形成,此时队首元素对应的值就是当前窗口的最大值。 这样,对于每一行来说,处理的时间是O(b),每个元素进出队列一次。 同样的,对于最小值处理,维护一个单调递增的队列。 这样,处理每一行的横向窗口的时间是O(b)。处理所有行的横向窗口的时间是O(ab)。 接下来,处理纵向窗口。假设我们现在有一个数组col_max,其行数是a,列数是b-n+1。我们需要对每一列进行处理,每个列的长度是a,需要处理每个纵向窗口的长度为n的窗口的最大值。例如,对于每一列j,处理所有可能的窗口,得到每个窗口的最大值,保存到最终的max_val数组中。这同样可以用单调队列,处理每一列的时间是O(a),总共有(b-n+1)列,所以总的时间是O(a*(b-n+1))),因为当a=1000,b=1000,n=100时,b-n+1=901,所以总时间大概是1000*901=约9e5次操作,这应该是可以接受的。 同样的,对rmin数组进行纵向处理,得到每个二维窗口的最小值。 最后,遍历所有可能的二维窗口的位置,计算对应的max_val[i][j] - min_val[i][j],并找出最小值。 综上,这就是使用两次单调队列处理的方法:第一次处理行,第二次处理列。这样,整个问题的时间复杂度是线性的,可以应对题目中的最大数据范围。 那现在需要考虑代码的具体实现。例如,如何存储原始矩阵?如何预处理行,然后列? 假设在C++中,原始矩阵存储为一个二维数组,比如vector<vector<int>> g(a, vector<int>(b))。 然后,预处理每一行的横向窗口的max和min,得到两个二维数组rmax和rmin。它们的行数是a,列数是b -n +1。 然后,预处理每一列的纵向窗口的max和min,得到最终的二维数组max_dp和min_dp,它们的行数是a -n +1,列数是b -n +1。每个元素对应原矩阵中的n×n窗口的max和min。 最后,遍历max_dp和min_dp中的每个元素,计算差值,找到最小值。 那么,如何实现行的预处理? 举个例子,处理行的横向窗口的max: 对于每一行i,遍历该行的每个元素j: 维护一个deque,保存当前窗口内可能成为最大值的元素的下标。窗口的范围是[j -n +1, j]。 比如,当处理到元素j时,窗口的左端是j -n +1。所以,当队列的头部元素的下标小于j -n +1时,需要弹出。 然后,将当前元素j的值与队列尾部的元素比较,如果当前元素更大,则弹出尾部。循环直到队列为空或者遇到更大的元素。 然后将j加入队列。 当j >=n-1时,说明窗口已经形成,此时的队首元素对应的值就是该窗口的最大值。将该值存入rmax[i][j -n +1]的位置?或者,假设rmax的行数与原矩阵相同,但每行只有b -n +1个元素? 或者,可能更直观的是,当处理完每一行之后,该行的横向窗口的max数组的长度是b -n +1。例如,对于行i,rmax[i]的长度是b -n +1,每个元素对应一个起始位置j的窗口的最大值。 或者,在代码实现中,对于每一行i,先处理得到该行的横向窗口的max数组,然后保存到rmax[i]中。例如: vector<vector<int>> rmax(a, vector<int>(b -n +1)); 同样的,rmin也是如此。 所以,对于每一行i,生成对应的rmax[i]数组的过程: 初始化一个deque,然后遍历该行的每个元素j(从0到b-1)。 当j >=n-1时,可以开始记录窗口的最大值。此时窗口的起始位置是j -n +1,对应到rmax[i]中的位置是j -n +1,即rmax[i][j -n +1] = row[deque.front()]. 或者,假设每个窗口的起始位置是k,那么k的范围是0 <=k <=b-n。对应的结束位置是k +n -1。 因此,当处理到元素j=k +n -1时,窗口k的结束位置是j,此时可以记录最大值到rmax[i][k]。这样,对于每个k,j的范围是从0到b-1,当k +n-1 ==j时,处理窗口k的最大值。 所以,这可能需要循环每个k,或者在处理元素j时,当j >=n-1时,对应的k=j -n +1。因此,在处理j时,如果j >=n-1,那么k的取值范围是0到j-n+1,这可能吗? 可能更简单的方法是,当处理到每个j时,维护一个窗口,窗口的右端是j,窗口的长度是n。因此,窗口的有效条件是从j -n +1到j的区间。当j >=n-1时,窗口有效。此时,队列中的头元素就是该窗口的最大值,所以可以将该值保存到rmax[i][j -n +1]。 例如,在处理行i时: deque<int> q; for(int j=0; j <b; j++){ // 移除队列中不在窗口内的元素的下标 while(!q.empty() && q.front() <= j -n){ q.pop_front(); } // 维护单调递减队列 while(!q.empty() && row[j] >= row[q.back()]){ q.pop_back(); } q.push_back(j); // 当j >=n-1时,可以记录当前窗口的最大值 if(j >=n-1){ rmax[i][j -n +1] = row[q.front()]; } } 这样,对于每一行i来说,处理完成后,rmax[i]的长度是b -n +1,每个元素对应起始位置k=j-n+1的窗口的最大值。 同样的,处理rmin[i]的时候,维护单调递增队列。 这样,行的处理就完成了。 然后,处理列的时候,对于每个列j(这里的j指的是横向处理后的列,即原列数经过横向处理后的列数为cols =b-n+1),处理每个列j的纵向窗口。 同样的,纵向窗口的长度是n。例如,对于列j,处理每个行i的rmax[i][j],维护一个纵向窗口的最大值。 此时,纵向窗口的处理方式与横向类似,但处理的是列数据。例如,对于每个列j,在rmax数组的该列中,行的数量是a,需要处理每个可能的纵向窗口的最大值,保存到max_dp数组中。 所以,处理列j时: 初始化一个deque,遍历行i从0到a-1。 对于每个i,维护一个窗口,窗口大小为n,当i >=n-1时,窗口的有效位置是i-n+1到i,此时记录该窗口的最大值到max_dp[i-n+1][j]中。 同样的,处理列j的每个元素i: max_dp的行数是a -n +1,列数是cols =b -n +1. 所以,处理列j的代码大致如下: vector<vector<int>> max_dp(a -n +1, vector<int>(cols)); for(int j=0; j <cols; j++){ deque<int> q; for(int i=0; i <a; i++){ while(!q.empty() && q.front() <= i -n){ q.pop_front(); } while(!q.empty() && rmax[i][j] >= rmax[q.back()][j]){ q.pop_back(); } q.push_back(i); if(i >=n-1){ max_dp[i -n +1][j] = rmax[q.front()][j]; } } } 同样的,处理rmin得到min_dp。 最后,遍历max_dp和min_dp的所有元素,计算差值,找到最小值。 这样,整个问题就解决了。 那在代码实现中,需要注意的问题: 1. 矩阵的行和列的索引是否正确处理。 2. 处理横向和纵向时的窗口范围是否正确。 3. 内存是否足够。例如,当a和b都是1000的时候,rmax和rmin的每个行是1000-100+1=901列,所以每个数组的大小是1000*901=约9e5,这在C++中是可行的。 此外,输入的数值可能很大,但比较的时候不需要担心溢出,因为都是整数比较。 现在,测试样例的输入: 输入样例1: 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=5,b=4,n=2。所以,每个可能的2x2窗口的max-min的最小差是1。比如,原矩阵中的某个窗口的最大和最小值相差1。 现在,让我们看看如何处理这个样例: 原矩阵是5行4列: 第一行:1 2 5 6 第二行:0 17 16 0 第三行:16 17 2 1 第四行:2 10 2 1 第五行:1 2 2 2 处理行横向窗口的max和min: 对于每一行,每个窗口长度为2。 比如,第一行的横向窗口: 窗口0-1:最大值是2,5,6?或者原行是1,2,5,6。那么每个横向窗口是: 窗口0: [1,2] → max=2,min=1 窗口1: [2,5] → max=5,min=2 窗口2: [5,6] → max=6,min=5 → 所以rmax[0] = [2,5,6], rmin[0] = [1,2,5] 其他行同理。例如,第二行: 0,17,16,0 → 窗口0:0,17 → max17,min0;窗口1:17,16 → max17,min16;窗口2:16,0 → max16,min0 →所以rmax[1]是17,17,16;rmin[1]是0,16,0。 然后,处理列,比如,对rmax的每一列进行纵向窗口处理。例如,假设对于rmax的第一列: 各行的值分别是2(第一行)、17(第二行)、17(第三行)、10(第四行)、2(第五行)。 当处理纵向窗口大小为2时,窗口的max如下: 窗口0:2和17 →max17 窗口1:17和17 →max17 窗口2:17和10 →max17 窗口3:10和2 →max10 所以,max_dp的第一列是17,17,17,10。然后对应的min_dp中的同一列是各行的rmin中的该列的最小值? 例如,rmin的第一列是第一行的1,第二行的0,第三行的16,第四行的2,第五行的1。纵向窗口处理后的min: 窗口0:1和0 → min0 窗口1:0和16 →min0 窗口2:16和2 →min2 窗口3:2和1 →min1 所以,对于每个窗口,max和min的差是: 比如窗口0的max17,min0,差17-0=17;窗口1的max17,min0,差17;窗口2的max17,min2 →15;窗口3的max10,min1 →9. 但是可能其他列会有更小的差。 比如,假设某列中的max和min的差是1,例如,当max和min相差1的时候。 比如,看样例的输出是1,可能是在某个窗口中,max和min的差为1。例如,在第三行的某个窗口,比如第三行原矩阵的窗口是17和2,最大值17,最小值2。但经过处理后,可能纵向窗口处理后的结果是某个差为1的情况。 需要更详细的分析。 不过,通过代码的正确处理,可以正确得到结果。 综上,代码的大体思路是正确的。接下来需要编写C++代码实现这一思路。需要注意的是数组的大小,以及正确处理行和列的处理顺序。 现在,关于代码的结构: 1. 输入a,b,n。 2. 读取矩阵。 3. 预处理每一行的横向max和min,得到rmax和rmin。 4. 预处理每一列的纵向max和min,得到max_dp和min_dp。 5. 遍历所有max_dp和min_dp的元素,计算差值,取最小。 代码实现的关键点在于正确使用单调队列处理行和列。 现在,编写代码的大体步骤: 在C++中,输入可能较大,可以使用快速读取的方式。但是根据题目中的输入样例,每个数之间用空格分隔,可以用cin或scanf读取。 首先,定义二维数组保存原始矩阵: int a, b, n; cin >>a >>b >>n; vector<vector<int>> g(a, vector<int>(b)); for(int i=0; i <a; i++){ for(int j=0; j <b; j++){ cin >>g[i][j]; } } 然后,处理每一行的横向窗口: int cols = b -n +1; vector<vector<int>> rmax(a, vector<int>(cols)); vector<vector<int>> rmin(a, vector<int>(cols)); for(int i=0; i <a; i++){ deque<int> q_max, q_min; for(int j=0; j <b; j++){ // 处理max队列 while(!q_max.empty() && j - q_max.front() >=n){ q_max.pop_front(); } while(!q_max.empty() && g[i][j] >= g[i][q_max.back()]){ q_max.pop_back(); } q_max.push_back(j); // 处理min队列 while(!q_min.empty() && j - q_min.front() >=n){ q_min.pop_front(); } while(!q_min.empty() && g[i][j] <= g[i][q_min.back()]){ q_min.pop_back(); } q_min.push_back(j); // 当j >=n-1时,可以记录结果 if(j >=n-1){ int index = j -n +1; rmax[i][index] = g[i][q_max.front()]; rmin[i][index] = g[i][q_min.front()]; } } } 这里需要注意,窗口的条件是当前元素j是否达到了窗口的右端。例如,当窗口的长度是n时,当j >=n-1时,窗口的左端是j-n+1,右端是j。这时候可以记录该窗口的最大和最小值。 这样处理后,rmax和rmin的每个行的列数是cols =b-n+1。 接下来,处理列的纵向窗口。例如,max_dp的行数是a-n+1,列数是cols。 int rows_final = a -n +1; vector<vector<int>> max_dp(rows_final, vector<int>(cols)); vector<vector<int>> min_dp(rows_final, vector<int>(cols)); for(int j=0; j <cols; j++){ deque<int> q_max, q_min; for(int i=0; i <a; i++){ // 处理max队列 while(!q_max.empty() && i - q_max.front() >=n){ q_max.pop_front(); } while(!q_max.empty() && rmax[i][j] >= rmax[q_max.back()][j]){ q_max.pop_back(); } q_max.push_back(i); // 处理min队列 while(!q_min.empty() && i - q_min.front() >=n){ q_min.pop_front(); } while(!q_min.empty() && rmin[i][j] <= rmin[q_min.back()][j]){ q_min.pop_back(); } q_min.push_back(i); // 当i >=n-1时,记录结果 if(i >=n-1){ int idx = i -n +1; max_dp[idx][j] = rmax[q_max.front()][j]; min_dp[idx][j] = rmin[q_min.front()][j]; } } } 这里,对于每个列j,遍历所有行i。当i >=n-1时,可以记录纵向窗口的最大和最小值到max_dp和min_dp的相应位置。 最后,遍历所有可能的窗口位置,计算差值: int ans = INT_MAX; for(int i=0; i <rows_final; i++){ for(int j=0; j <cols; j++){ ans = min(ans, max_dp[i][j] - min_dp[i][j]); } } cout <<ans <<endl; 这样,整个代码的结构就完成了。 测试样例的输入是否正确处理? 比如,输入样例中的a=5,b=4,n=2。cols=4-2+1=3。rows_final=5-2+1=4. 处理完行的rmax和rmin后,处理列的max_dp和min_dp,得到4行3列的数组。 然后遍历每个元素,找出最小的差值。 例如,在样例中,可能存在某个窗口的max和min差为1,所以输出1。 综上,这样的代码应该可以正确处理该问题。 需要注意的边界情况: 1. a或b等于n的情况。例如,当a=n时,rows_final=1,同样处理。 2. n=1的情况?但题目中n>=2。根据题目描述,输入保证n<=a, n<=b,并且2<=a,b<=1000,所以n的最小值是2? 或者题目中的输入条件是否允许n=1?题目描述中的输入格式中的n的说明:题目保证n<=a, n<=b,且样例中n=2。而题目中的n的取值可能根据实际情况而变化。例如,原题的数据范围中,n可以是1吗? 但根据题目中的输入描述,输入的n是可能的,比如用户可能给出n=1。但根据题目中的说明,数据范围中n<=a和n<=b,但题目中的输入描述中的n可能大于等于1? 但根据题目的输出要求,输出的正方形区域是n×n,当n=1时,每个元素自己构成一个正方形,此时差为0。这种情况下是否代码能正确处理? 但根据题目中的输入数据范围,题目说明中给出100%的数据中n<=100,而a和b>=2,所以n的可能取值在2到100之间?或者题目中的n的取值范围可能更广? 需要看原题的输入数据范围。根据题目中的说明: 20%的数据:2 <=a,b <=100, n <=a, n <=b, n <=10. 100%的数据:2 <=a,b <=1000, n <=a, n <=b, n <=100. 因此,n的取值范围是1<=n <=a,n<=b?或者题目中的n必须至少是2? 例如,题目描述中的输入格式中,n是3个整数中的第三个,而输出要求的是n×n的正方形。可能n可以等于1? 但根据样例,n=2,所以代码必须处理n>=1的情况。比如,当n=1时,每个单独元素构成窗口,此时每个窗口的max和min都是该元素的值,差为0。所以代码是否能处理这种情况? 例如,当n=1时,cols =b -1 +1 =b,即与原列数相同。行的处理是每个元素的窗口长度为1,所以每个位置的rmax和rmin等于该元素本身。然后,列处理同样是每个元素的窗口长度为1,所以最终的max_dp和min_dp等于rmax和rmin的值。这样,每个元素对应的差是0,所以输出0。这种情况下,代码是正确的。 所以,代码中的处理应该可以正确处理n=1的情况。因此,不需要特别处理n=1的情况。 综上,这段代码应该可以正确解决问题。现在,编写正确的C++代码即可。</think>要解决这个问题,我们需要找到一个 $n \times n$ 的正方形区域,使得该区域内最大值和最小值的差最小。为了高效处理这个问题,我们可以使用单调队列来优化时间复杂度。 ### 方法思路 1. **横向处理**:首先对每一行使用单调队列预处理每个长度为 $n$ 的窗口的最大值和最小值。这一步将二维问题转化为一维问题。 2. **纵向处理**:接着对每一列使用单调队列处理预处理后的结果,进一步得到每个 $n \times n$ 正方形的最大值和最小值。 3. **计算结果**:遍历所有可能的 $n \times n$ 正方形区域,计算其最大值和最小值的差,找出最小值。 ### 解决代码 ```cpp #include <iostream> #include <vector> #include <deque> #include <climits> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int a, b, n; cin >> a >> b >> n; vector<vector<int>> g(a, vector<int>(b)); for (int i = 0; i < a; ++i) { for (int j = 0; j < b; ++j) { cin >> g[i][j]; } } // 预处理每一行的横向窗口的最大和最小值 int cols = b - n + 1; vector<vector<int>> rmax(a, vector<int>(cols)); vector<vector<int>> rmin(a, vector<int>(cols)); for (int i = 0; i < a; ++i) { deque<int> q_max, q_min; for (int j = 0; j < b; ++j) { // 维护单调递减队列(最大值) while (!q_max.empty() && j - q_max.front() >= n) { q_max.pop_front(); } while (!q_max.empty() && g[i][j] >= g[i][q_max.back()]) { q_max.pop_back(); } q_max.push_back(j); // 维护单调递增队列(最小值) while (!q_min.empty() && j - q_min.front() >= n) { q_min.pop_front(); } while (!q_min.empty() && g[i][j] <= g[i][q_min.back()]) { q_min.pop_back(); } q_min.push_back(j); // 当窗口形成时记录结果 if (j >= n - 1) { int idx = j - n + 1; rmax[i][idx] = g[i][q_max.front()]; rmin[i][idx] = g[i][q_min.front()]; } } } // 预处理每一列的纵向窗口的最大和最小值 int rows_final = a - n + 1; vector<vector<int>> max_dp(rows_final, vector<int>(cols)); vector<vector<int>> min_dp(rows_final, vector<int>(cols)); for (int j = 0; j < cols; ++j) { deque<int> q_max, q_min; for (int i = 0; i < a; ++i) { // 处理最大值队列 while (!q_max.empty() && i - q_max.front() >= n) { q_max.pop_front(); } while (!q_max.empty() && rmax[i][j] >= rmax[q_max.back()][j]) { q_max.pop_back(); } q_max.push_back(i); // 处理最小值队列 while (!q_min.empty() && i - q_min.front() >= n) { q_min.pop_front(); } while (!q_min.empty() && rmin[i][j] <= rmin[q_min.back()][j]) { q_min.pop_back(); } q_min.push_back(i); // 当窗口形成时记录结果 if (i >= n - 1) { int idx = i - n + 1; max_dp[idx][j] = rmax[q_max.front()][j]; min_dp[idx][j] = rmin[q_min.front()][j]; } } } // 计算最小差值 int ans = INT_MAX; for (int i = 0; i < rows_final; ++i) { for (int j = 0; j < cols; ++j) { ans = min(ans, max_dp[i][j] - min_dp[i][j]); } } cout << ans << endl; return 0; } ``` ### 代码解释 1. **输入处理**:读取矩阵的维度和数据。 2. **横向预处理**:使用单调队列处理每一行的每个窗口,得到每个窗口的最大值和最小值。 3. **纵向预处理**:使用单调队列处理每一列的每个窗口,得到最终的二维窗口的最大值和最小值。 4. **结果计算**:遍历所有二维窗口,计算最大值和最小值的差,找出最小值。 这种方法通过两次单调队列处理将时间复杂度优化到线性级别,能够高效处理大规模数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值