【HAOI2007】理想的正方形

时间:2021-07-04 12:27:44

【问题描述】

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

【输入】

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

【输出】

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

分析】

单调队列,先处理横行,再处理竖行。

 #include <cstdlib>
#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
const int MAX=;
const int INF=0x7fffffff;
using namespace std;
//注意Max与Min(i,j)代表第i行从
int data[MAX][MAX],Max[MAX][MAX],Min[MAX][MAX];
int a,b,n,ans=INF; void init();//输入数据
void solve();
void prepare(int hang);
void work(int lie); int main()
{
//文件操作
freopen("square.in","r",stdin);
freopen("square.out","w",stdout);
init();//输入数据
solve();//求解
return ;
}
void init()
{
scanf("%d%d%d",&a,&b,&n);
for (int i=;i<=a;i++)
for (int j=;j<=b;j++)
scanf("%d",&data[i][j]);
}
void solve()
{
//计算出每行各个元素的最大值与最小值
for (int i=;i<=a;i++) prepare(i);//横推行
// printf("\n");
for (int i=n;i<=b;i++) work(i);//纵推列
printf("%d\n",ans);
}
void prepare(int hang)
{
int Q_MAX[MAX],front_MAX=,rear_MAX=;
int Q_MIN[MAX],front_MIN=,rear_MIN=;
for (int i=;i<=n;i++)//预处理
{
while (front_MAX<rear_MAX && data[hang][Q_MAX[rear_MAX]]<data[hang][i]) rear_MAX--;
Q_MAX[++rear_MAX]=i;
while (front_MIN<rear_MIN && data[hang][Q_MIN[rear_MIN]]>data[hang][i]) rear_MIN--;
Q_MIN[++rear_MIN]=i;
}
//开始计算,千万要注意边界问题
for (int i=n;i<=b;i++)
{
//先计算MAX与MIN值
while (front_MAX<rear_MAX && Q_MAX[front_MAX+]<(i-n+)) front_MAX++;
Max[hang][i]=data[hang][Q_MAX[front_MAX+]];
while (front_MIN<rear_MIN && Q_MIN[front_MIN+]<(i-n+)) front_MIN++;
Min[hang][i]=data[hang][Q_MIN[front_MIN+]];
//再考虑加入队列
while (front_MAX<rear_MAX && data[hang][Q_MAX[rear_MAX]]<data[hang][i+]) rear_MAX--;
Q_MAX[++rear_MAX]=i+;
while (front_MIN<rear_MIN && data[hang][Q_MIN[rear_MIN]]>data[hang][i+]) rear_MIN--;
Q_MIN[++rear_MIN]=i+;
}
//打印
//for (int i=n;i<=b;i++) printf("(%d %d) ",Max[hang][i],Min[hang][i]);
// printf("\n");
}
void work(int lie)
{
//printf("%d:",lie);
int Q_MAX[MAX],front_MAX=,rear_MAX=;
int Q_MIN[MAX],front_MIN=,rear_MIN=;
for (int i=;i<=n;i++)//预处理,注意这里换成行了
{
while (front_MAX<rear_MAX && Max[Q_MAX[rear_MAX-]][lie]<Max[i][lie]) rear_MAX--;
Q_MAX[rear_MAX++]=i;
while (front_MIN<rear_MIN && Min[Q_MIN[rear_MIN-]][lie]>Min[i][lie]) rear_MIN--;
Q_MIN[rear_MIN++]=i;
}
//开始计算,千万要注意边界问题
for (int i=n;i<=a;i++)
{
while (front_MAX<rear_MAX && Q_MAX[front_MAX]<(i-n+)) front_MAX++;
while (front_MIN<rear_MIN && Q_MIN[front_MIN]<(i-n+)) front_MIN++;
ans=min(ans,Max[Q_MAX[front_MAX]][lie]-Min[Q_MIN[front_MIN]][lie]); //printf("(%d %d) ",Q_MAX[front_MAX],Q_MIN[front_MIN]);
//再考虑加入队列
while (front_MAX<rear_MAX && Max[Q_MAX[rear_MAX-]][lie]<Max[i+][lie]) rear_MAX--;
Q_MAX[rear_MAX++]=i+;
while (front_MIN<rear_MIN && Min[Q_MIN[rear_MIN-]][lie]>Min[i+][lie]) rear_MIN--;
Q_MIN[rear_MIN++]=i+;
}
//printf("\n");
}