【试炼场】理想的正方形【单调队列】【矩阵DP】

传送门

题目大意

给出一个A × \times ×B的矩阵,每个格子有一个值,要在其中找出一个N × \times ×N的正方形,使得这个正方形中最大值与最小值的差最小。
A,B<=1000

题解

难得一次性想到正解

因为没什么好的想法,我们直接考虑怎么统计每一个符合条件的正方形里的最大值与最小值。那这样的话,不用暴力怎么做呢??

我们先来想另一个问题:假设这道题给的不是矩形而是数列,你会做吗??

然后你会发现:这个不就是某滑动窗口吗?

没错,既然如此,我们现在只需要考虑一下怎么把它扩展到二维。举样例说明:

在这里插入图片描述

(图片来源:洛谷P2216题解区)

①对于每一行,用单调队列找出每个长度为N的连续序列的最大值与最小值,存在这个序列的末尾。(原数组 更新 行)

②对于每一列,用单调队列找出每个长度为N的连续序列的最大值与最小值,存在这个序列的末尾。(行 更新 列)(这有点压缩维度的感觉,因为此时统计的信息已经可以代表一个正方形的信息了)

③N^2扫一下统计出答案

代码实现:

#include<bits/stdc++.h>
#define rint register int
#define ivoid inline void
#define iint inline int
#define endll '\n'
#define ll long long
using namespace std;
const int N=1e6+5;
const int M=3e3+5;
const int inf=0x3f3f3f3f;
int l,r,x,y,m,n,u,v,w,s,t;
int a[N],b[M][M],lmx[M][M],lmn[M][M],cmx[M][M],cmn[M][M];
// l(ine) 为行  c(olumn) 为列
int ans,sum,res,tot,cnt,num;

iint rad()
{
	int x=0,f=1;char c;
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
signed main()
{
	typedef pair<int,int> pii;
	deque<pii> q1,q2,q3,q4;
	n=rad();m=rad();x=rad();
	for(rint i=1;i<=n;i++){
		for(rint j=1;j<=m;j++){
			b[i][j]=rad();
	    }
	    //这里是每读入一行就顺便处理了,全部读入再处理也可以的
	    while(!q1.empty())q1.pop_back();
	    while(!q2.empty())q2.pop_back();
	    for(rint j=1;j<=m;j++){
	    	while(!q1.empty()&&q1.back().first<=b[i][j])q1.pop_back();
	    	q1.push_back((pii){b[i][j],j});
	    	while(q1.front().second<=j-x)q1.pop_front();
	    	if(j>=x)lmx[i][j-x+1]=q1.front().first;	    
	    		
			while(!q2.empty()&&q2.back().first>=b[i][j])q2.pop_back();
	    	q2.push_back((pii){b[i][j],j});
	    	while(q2.front().second<=j-x)q2.pop_front();
	    	if(j>=x)lmn[i][j-x+1]=q2.front().first;
		}
	}
	
	//一轮统计过后,我们按列来扫,这样就可以统计每一格作为正方形的右下角时的最大值与最小值  
	for(rint i=1;i<=m-x+1;i++){
		while(!q3.empty())q3.pop_back();
	    while(!q4.empty())q4.pop_back();
	    for(rint j=1;j<=n;j++){
	    	while(!q3.empty()&&q3.back().first<=lmx[j][i])q3.pop_back();
	    	q3.push_back((pii){lmx[j][i],j});
	    	while(q3.front().second<=j-x)q3.pop_front();
	    	if(j>=x)cmx[j-x+1][i]=q3.front().first;	    	
	    	
			while(!q4.empty()&&q4.back().first>=lmn[j][i])q4.pop_back();
	    	q4.push_back((pii){lmn[j][i],j});
	    	while(q4.front().second<=j-x)q4.pop_front();
	    	if(j>=x)cmn[j-x+1][i]=q4.front().first;
		}
	}
	//最后找到需要的答案输出即可 
	ans=inf;
	for(rint i=1;i<=n-x+1;i++){
		for(rint j=1;j<=m-x+1;j++){
			ans=min(cmx[i][j]-cmn[i][j],ans);
		}
	} 
	cout<<ans;	
}

总结

单调队列+DP不是没见过,但是遇到二维状况还要运用压缩维度的思想就很特别了,好题一道~
另外推荐下这道题:P2219 [HAOI2007]修筑绿化带,非常相似的解题思路哦~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值