[HAOI2007] 理想的正方形 解题报告

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

(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;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值