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

传送门

题目大意

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

题解

难得一次性想到正解

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

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

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

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

【试炼场】理想的正方形【单调队列】【矩阵DP】_第1张图片

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

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

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

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

代码实现:

#include
#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]修筑绿化带,非常相似的解题思路哦~

你可能感兴趣的:(试炼场,矩阵DP,单调队列)