308. [HAOI2007] 理想的正方形
★★ 输入文件:square.in 输出文件:square.out 简单对比
时间限制:2 s 内存限制:128 MB
【问题描述】
有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
【输入】:
第一行为3个整数,分别表示a,b,n的值
第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。
【输出】:
仅一个整数,为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
【数据范围】
(1)矩阵中的所有数都不超过1,000,000,000
(2)20%的数据2<=a,b<=100,n<=a,n<=b,n<=10
★★ 输入文件:square.in 输出文件:square.out 简单对比
时间限制:2 s 内存限制:128 MB
【问题描述】
有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
【输入】:
第一行为3个整数,分别表示a,b,n的值
第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。
【输出】:
仅一个整数,为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
【数据范围】
(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
代码中有详尽注释:
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int maxn=1010;
const int INF=0x7fffffff;
int a[maxn][maxn];
int maxv[maxn][maxn];//表示第i行, 以j结尾的, 长度为k的一段长度的最大值;
int minv[maxn][maxn];//与maxv相似;
int n, m, k, ans=INF;
int q1[maxn], q2[maxn], q[maxn];
int main()
{
freopen("square.in", "r", stdin);
freopen("square.out", "w", stdout);
scanf("%d%d%d", &n, &m, &k);
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++) scanf("%d", &a[i][j]);
for(int i=1; i<=n; i++) {//求最大值;
int h=0, t=0;//队首队尾下标初始化为0;
q[0]=0; // q[0]最好初始化为0,以免出现错误;
//原因就是之前用过这个队列, 队列中可能还有元素;
a[i][0]=-INF; //因为第一次比较a[i][q[t]]<=a[i][j] 时 q[t]=0;
//所以a[i][0]初始化为-INF,这样才能将第一个元素加入队列;
for(int j=1; j<=m; j++) {
while(h<=t && a[i][q[t]]<=a[i][j]) t--;//若队尾元素小于a[i][j], 则删除队尾元素;
//因为求最大值时, 队列中维护的是 递减的值的编号
q[++t]=j;
while(h<t && j-q[h]>=k) h++; //删除没有在区间中的编号;
if(j>=k) maxv[i][j]=a[i][q[h]];//保存最大值;
}
}
for(int i=1; i<=n; i++) {//求最小值,与求最大值相似;
int h=0, t=0;
a[i][0]=INF; q[0]=0;
for(int j=1; j<=m; j++) {
while(h<=t && a[i][q[t]]>=a[i][j]) t--;
q[++t]=j;
while(h<t && j-q[h]>=k) h++;
if(j>=k) minv[i][j]=a[i][q[h]];
}
}
/*for(int j=k; j<=m; j++) {
for(int i=k; i<=n; i++) {
int Max=0, Min=INF;
for(int p=i; p>i-k; p--)
Max=max(Max, maxv[p][j]), Min=min(Min, minv[p][j]);
ans=min(ans, Max-Min);
}
}*/// 之前我是这么写的, 就是暴力枚举正方形右下角坐标, 更新ans;
/// 没想到这么破的复杂度都能过-_-
for(int j=k; j<=m; j++) {//这是优化过的, 更新答案也能用单调队列
//其实就是把刚才统计行的max,min变成了统计列的max,min;
//上面的代码理解啦, 这里的也就不难理解啦;
int h1=0, t1=0, h2=0, t2=0;
maxv[0][j]=-INF; minv[0][j]=INF;
q1[0]=0; q2[0]=0; // very important, 不加这句话就会出现奇奇怪怪的错;
//原因就是之前用过这个队列, 队列中可能还有元素;
for(int i=1; i<=n; i++) {
while(h1<=t1 && maxv[q1[t1]][j]<=maxv[i][j]) t1--;
while(h2<=t2 && minv[q2[t2]][j]>=minv[i][j]) t2--;
q1[++t1]=i; q2[++t2]=i;
while(h1<t1 && i-q1[h1]>=k) h1++;
while(h2<t2 && i-q2[h2]>=k) h2++;
if(i>=k) ans=min(ans, maxv[q1[h1]][j]-minv[q2[h2]][j]);
}
}
printf("%d", ans);
return 0;
}