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

题目链接

https://www.luogu.org/problem/P2216

题意

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

思路

针对每一行维护一个单调队列(类似于维护一个滑动窗口),利用deque单调递增时维护该行当前滑动窗口的最小值的位置(注意不是最小值),利用deque单调递减时维护该行当前滑动窗口的最大值的位置(注意不是最大值)
然后吸吸氧就过了…

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<algorithm>
#include<set>
#include<queue>
#include<deque>
#include<vector>
#define INF 0x3f3f3f3f
#define N 1010
using namespace std;
deque<int> Q[N],Q2[N];//分别维护给定滑动窗口的最小值和最大值的位置

int mz[N][N];
int mn[N][N],mx[N][N];//记录每一行给定位置的最大值与最小值

int a,b,n;
int main() {
//	freopen("p.txt","r",stdin);
	scanf("%d%d%d",&a,&b,&n);
	for(int i=1; i<=a; i++) {
		for(int j=1; j<=b; j++) {
			scanf("%d",&mz[i][j]);
		}
	}
	//单调队列维护下标
	for(int i=1; i<=a; i++) {
		for(int j=1; j<=b; j++) {
			int now=mz[i][j];
			while(!Q[i].empty() && now<=mz[i][Q[i].back()]) {//注意这里不是最小值的位置,而是最小值
				Q[i].pop_back();
			}
			Q[i].push_back(j);//维护窗口里面每个值的位置索引
			while(Q[i].back()-Q[i].front()+1>n) Q[i].pop_front();
			if(j>=n) mn[i][j]=mz[i][Q[i].front()];

			//下降
			while(!Q2[i].empty() && now>=mz[i][Q2[i].back()]) Q2[i].pop_back();
			Q2[i].push_back(j);
			while(Q2[i].back()-Q2[i].front()+1>n) Q2[i].pop_front();
			if(j>=n) mx[i][j]=mz[i][Q2[i].front()];
		}
	}		
	int ans=INF;
	for(int j=n; j<=b; j++) {
		for(int i=1; i+n-1<=a; i++) {
			int mxv=-1,mnv=INF;
			for(int d=0; d<n; d++) mxv=max(mxv,mx[i+d][j]),mnv=min(mnv,mn[i+d][j]);
			ans=min(ans,mxv-mnv);
		}
	}

	printf("%d\n",ans);
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值