[HAOI2007]理想正方形-单调队列学习笔记

64 篇文章 0 订阅
40 篇文章 0 订阅

单调队列是一种限制条件比较多但是在某些问题上应用有意想不到的效果的数据结构。

这种数据结构的核心思想也是减少不必要的比较。考虑luoguP1886 右转进入原题这个滑动窗口。

以最大值为例,当一个窗口中的元素右边有一个比它更大的元素(也在窗口中),那么(当它右边的元素进入窗口中之后)无论何时这个元素都不能成为最大值。

那我们就可以把它删除。入队实现起来就是这样的,如果队不空且要入队的元素比队首元素大,那么rp--。重复上述操作至不能重复为止。然后正常入队。

这样,复杂度由于每个元素进出队只有一次,是O(n)的。

代码实现上的一个小技巧,就是队列中存的不是元素的值,而是元素(在原数组)的下标。这样一来,pop操作就要简单得多,只不过很容易写错,所以要时刻提醒自己,队列中存的是下标!

先是一维的滑动窗口/单调队列,以luoguP1886(链接见上文)

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 1000010
using namespace std;
template<typename Tp>//维护最大值的单调队列
struct mtc_queue{
	private:
		int fp,rp,q[MAXN];
		Tp v[MAXN];int vsz;
	public:
		mtc_queue()
		{
			fp=1;rp=0;vsz=0;
		}
		inline int clear()
		{
			fp=1;rp=0;vsz=0;
		}
		inline bool empty()
		{
			return (rp+1)==fp?true:false;
		}
		inline int push(const Tp &val)
		{
			while(!this->empty()&&v[q[rp]]<=val) rp--;
			v[q[++rp]=++vsz]=val;
		}
		inline void pop(int pos)
		{
			if(empty()) printf("OUT OF RANGE!\n");
			else if(pos==q[fp]) fp++;
		}
		inline Tp front()
		{
			if(empty()) printf("OUT OF RANGE!\n");
			return v[q[fp]];
		}
};
mtc_queue<int> maxq,minq;
int n,k,a[MAXN],minans[MAXN],maxans[MAXN];
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=k-1;i++) maxq.push(a[i]);
	for(int i=1;i<=k-1;i++) minq.push(-a[i]);
	for(int i=k;i<=n;i++)
	{
		maxq.push(a[i]);maxans[i]=maxq.front();
		minq.push(-a[i]);minans[i]=-minq.front();
		maxq.pop(i-k+1);minq.pop(i-k+1);
	}
	for(int i=k;i<=n;i++)
		if(i==n) printf("%d\n",minans[i]);
		else printf("%d ",minans[i]);
	for(int i=k;i<=n;i++)
		if(i==n) printf("%d\n",maxans[i]);
		else printf("%d ",maxans[i]);
	return 0;
}
然后,以[HAOI2007]理想的正方形 右转进入题目为例,简单的展示一下二维滑动窗口。

其实也很简单,先把一维的行的滑动窗口弄出来,然后在这个行的基础上对列求,就是正方形了。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define INF 1000000000
#define MAXN 1010
using namespace std;
template<typename Tp>//维护最大值的单调队列 
struct mtc_queue{
	private:
		int fp,rp,q[MAXN];
		Tp v[MAXN];int vsz;
	public:
		mtc_queue()
		{
			fp=1;rp=0;vsz=0;
		}
		inline void clear()
		{
			fp=1;rp=0;vsz=0;
		}
		inline bool empty()
		{
			return (rp+1)==fp?true:false;
		}
		inline void push(const Tp &val)
		{
			while(!this->empty()&&v[q[rp]]<=val) rp--;
			v[q[++rp]=++vsz]=val;
		}
		inline void pop(int pos)
		{
			if(empty()) printf("OUT OF RANGE!\n");
			else if(pos==q[fp]) fp++;
		}
		inline Tp front()
		{
			if(empty()) printf("OUT OF RANGE!\n");
			return v[q[fp]];
		}
};
mtc_queue<int> q;
int n,m,k;
int a[MAXN][MAXN];
int row[MAXN][MAXN];
int ans[MAXN][MAXN][2];//0 max 1 min
inline int get_row(int wgt)
{
	for(int i=1;i<=n;i++)
	{
		q.clear();
		for(int j=1;j<=k-1;j++)
			q.push(wgt*a[i][j]);
		for(int j=k;j<=m;j++)
		{
			q.push(wgt*a[i][j]);
			row[i][j]=wgt*q.front();
			q.pop(j-k+1);
		}
	}
	return 0;
}
inline int get_ans(int wgt)
{
	int no=(wgt==1?0:1);
	for(int j=k;j<=m;j++)
	{
		q.clear();
		for(int i=1;i<=k-1;i++)
			q.push(wgt*row[i][j]);
		for(int i=k;i<=n;i++)
		{
			q.push(wgt*row[i][j]);
			ans[i][j][no]=wgt*q.front();
			q.pop(i-k+1);
		}
	}
	return 0;
}
int main()
{
	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]);
	get_row(1);get_ans(1);//max
	get_row(-1);get_ans(-1);//min
	int minans=INF;
	for(int i=k;i<=n;i++)
		for(int j=k;j<=m;j++)
			if(ans[i][j][0]-ans[i][j][1]<minans)
				minans=ans[i][j][0]-ans[i][j][1];
	printf("%d\n",minans);return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值