二维单调队列

理想的正方形

二维单调队列

这道题目其实和dp其实没什么关系了,主要是一个二维单调队列求最值的问题
题意:求一个二维矩阵中,所有边长为n的正方形中 最大值 − 最小值 最大值-最小值 最大值最小值,最大为多少。如下图,即求所有这样蓝色正方形中, 最大值 − 最小值 最大值-最小值 最大值最小值的最大的值。

[外链图片转存中…(img-jA2pswOO-1722327180398)]

最暴力的想法是,枚举每一个正方形,然后再求出正方形中的最大值和最小值。这样时间复杂度是 O ( n 2 a b ) O(n^2ab) O(n2ab)。显然会超时,接下来考虑如何去优化这个问题。

最大值和最小值是同一个问题,这里只考虑如何求最大值,最小值只需要对称去做即可。

思路参考y总视频
对于这个二维的问题我们不妨先从一维入手。
对于一维问题,我们可以用单调队列去求,这可以先把二维的每一行都看成单独的序列,先用单调队列求出最值存储在每一行的右端点。如下图。

Screenshot_2024-07-30-15-25-02-317_com.jideos.jno.png

每一行长度为n的滑动窗口的最大值都存储在同一列上。
这时只需要再对上面结果矩阵~~(求出每一行长度为n的滑动窗口最大值的矩阵)~~按列方向再求一遍长度为n的滑动窗口操作,这样得到的新的矩阵就是边长为n的正方形的最大值矩阵,如下图。
Screenshot_2024-07-30-15-33-09-507_com.jideos.jno.png

最后再套用滑动窗口模板对最大值、最小值求两遍,就能得到所有边长为n的正方行的最大值、最小值。

再分析一下时间复杂度,瓶颈在求所有边长为n的正方形的最大值、最小值。
再求最大值的过程中是分别对每一行都做一遍滑动窗口,做一遍滑动窗口的时间复杂度是 O ( n ) O(n) O(n),这里需要做 n n n,所以时间复杂度是 O ( n 2 ) O(n^2) O(n2)
那么总的时间复杂度也就是 O ( n 2 ) O(n^2) O(n2)

#include<bits/stdc++.h>
#define rep(x,y,z)	for(int x=(y);x<=z;x++)
#define fep(x,y,z)	for(int x=(y);x>=z;x--)
#define db double
#define ll long long
using namespace std;
const int N=1010,M=N<<1;
int n,m,k;
int w[N][N],minv[N][N],maxv[N][N],a[N],b[N],c[N],d[N];
int q[N],hh,tt;
void get_max(int a[],int f[],int n)
{
	hh=0,tt=-1;
	rep(i,1,n)
	{
		while(hh<=tt&&i-q[hh]>k-1)	hh++;//窗口长度大于k,队头出队
		while(hh<=tt&&a[i]>=a[q[tt]])	tt--;//去除队列中冗余元素
		q[++tt]=i;//入队
		f[i]=a[q[hh]];//更新滑动窗口的答案
	}
}

void get_min(int a[],int f[],int n)
{
	hh=0,tt=-1;
	rep(i,1,n)
	{
		while(hh<=tt&&i-q[hh]>k-1)	hh++;//窗口长度大于k,队头出队
		while(hh<=tt&&a[i]<=a[q[tt]])	tt--;//去除队列中冗余元素
		q[++tt]=i;//入队
		f[i]=a[q[hh]];//更新滑动窗口的答案
	}
}

void solve()
{
	cin>>n>>m>>k;
	rep(i,1,n)	rep(j,1,m)	cin>>w[i][j];
	rep(i,1,n)//对每一行做滑动窗口,求出最值
	{
		get_min(w[i],minv[i],m);
		get_max(w[i],maxv[i],m);
	}
	ll res=1e18;
	rep(i,k,m)//对每一列做滑动窗口,求出最值的最值
	{
		rep(j,1,n)//抠出来每一列	
		{
			a[j]=minv[j][i];
			b[j]=maxv[j][i];
		}
		get_min(a,c,n);
		get_max(b,d,n);
		rep(j,k,n)	res=min(1ll*d[j]-c[j],res);	
	}
	cout<<res<<'\n';
} 
int main()
{
//   	freopen("1.in","r",stdin);
	ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
	solve();
	return 0;
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值