题目描述
有一个 (a imes b)的整数组成的矩阵,现请你从中找出一个 (n imes n) 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
输入格式
第一行为 (3) 个整数,分别表示 (a,b,n) 的值
第二行至第 (a+1) 行每行为 (b) 个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。
输出格式
仅一个整数,为 (a imes b) 矩阵中所有“ (n imes n) 正方形区域中的最大整数和最小整数的差值”的最小值。
输入输出样例
输入 #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) 的这一块矩形中的最大值与最小值。
你可以理解为,把 (n) 个长度为 (n) 的区间拼在了一起。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int ans = 2147483647;
int n,m,k,a[1010][1010],q1[1010],q2[1010];
int maxn[1010][1010],minn[1010][1010];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
return s * w;
}
int main()
{
n = read(); m = read(); k = read();
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) a[i][j] = read();
for(int i = 1; i <= n; i++)
{
int l = 1, r = 0, L = 1, R = 0;
q1[++r] = 1; q2[++R] = 1;
for(int j = 2; j <= m; j++)//对每一行求一遍滑动窗口
{
while(l <= r && q1[l] <= j-k) l++;
while(L <= R && q2[L] <= j-k) L++;
while(l <= r && a[i][q1[r]] <= a[i][j]) r--;
while(L <= R && a[i][q2[R]] >= a[i][j]) R--;
q1[++r] = j; q2[++R] = j;
maxn[i][j] = a[i][q1[l]];
minn[i][j] = a[i][q2[L]];
}
}
for(int i = k; i <= m; i++)
{
int l = 1, r = 0, L = 1, R = 0;
q1[++r] = 1; q2[++R] = 1;
for(int j = 2; j <= n; j++)//在每一行的基础上在对每一列做一遍滑动窗口
{
while(l <= r && q1[l] <= j-k) l++;
while(L <= R && q2[L] <= j-k) L++;
while(l <= r && maxn[q1[r]][i] <= maxn[j][i]) r--;
while(L <= R && minn[q2[R]][i] >= minn[j][i]) R--;
q1[++r] = j; q2[++R] = j;
if(j >= k) ans = min(ans,maxn[q1[l]][i] - minn[q2[L]][i]);
}
}
printf("%d
",ans);
return 0;
}