单调队列 & 单调队列优化DP & 题目推荐

来看一道题

扫描

题目描述
有一个 1 ∗ n 的矩阵,有 n 个正整数。

现在给你一个可以盖住连续的 k 的数的木板。

一开始木板盖住了矩阵的第 1 ∼ k 个数,每次将木板向右移动一个单位,直到右端与第 n 个数重合。

每次移动前输出被覆盖住的最大的数是多少。

输入格式
第一行两个数,n,k,表示共有 n 个数,木板可以盖住 k 个数。

第二行 n 个数,表示矩阵中的元素。

输出格式
共 n − k + 1 行,每行一个正整数。

第 i 行表示第 i ∼ i + k − 1 个数中最大值是多少。

说明/提示
对于 100% 的数据保证:1 ≤ n ≤ 2 ∗ 1e6,1 ≤ k ≤ n

矩阵中元素大小不超过 1e4。


这道题我们很明显是没有办法暴力的(极限复杂度 O ( N 2 ) O(N^2)

那么我们就要想办法优化,这时候就需要引入我们的单调队列

单调队列

我们需要维护一个队列让他保持单调性(递增或者递减)

形象的来说就是一些帅哥在比谁长得帅,每个人都有一个身高和颜值

首先进来的是xyc,他的身高是1m,颜值是 -\infty ,但是因为队里是空的,所以我们还是让他入队

队列里变成

编号 姓名 身高 颜值
1 xyc 1m -\infty

后来又来了一个gjm,他的身高是1.1m,颜值是0,那么,xyc既比gjm矮,又没有gjm帅,所以xyc肯定不是这次“比帅大会”的冠军了,所以我们把他踢出队列

队列变成

编号 姓名 身高 颜值
2 gjm 1.1m 0 0

后来又来了一个ygl,ygl身高1.7m,颜值是 100 -100

虽然ygl颜值比gjm要低,但是他比gjm高啊,海拔也可以体现帅,所以gjm仍然有希望,我们把ygl加入队列

编号 姓名 身高 颜值
2 gjm 1.1m 0 0
3 ygl 1.7m 100 -100

最后来了一个ljr,ljr身高一米九,帅度 + +\infty

这样他轻松吊打了[吊打了(吊打了集训队的xyc)的gjm],成为了这次比赛的冠军,gjm和ygl只能被踢出,因为他们身高和颜值都没有ljr高

编号 姓名 身高 颜值
4 ljr 1.9m + +\infty

经过这个例子我们就知道了单调队列的思想,有两个参数 a i a_i b i b_i ,如果有一个j满足 a i < a j a_i <a_j 并且 b i < b j b_i<b_j ,那么我们就把i给踢出队列

那么我们再回到上面那道题

我们维护一个单调队列,里面都是区间最大值,但是我们要让队列里面单调递减,这样每一次得到的队头就是区间最大值

单调队列可以通过 S T L STL 来实现,但是通常需要用到双端队列 d e q u e deque

一般还是推荐大家手写队列

P2032代码

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <cmath>
# include <climits>
# include <iostream>
# include <string>
# include <queue>
# include <vector>
# include <set>
# include <map>
# include <cstdlib>
# include <stack>
# include <ctime>
using namespace std;

# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)
# define mct(a,b) memset(a,b,sizeof(a))
# define gc getchar()
typedef long long ll;
const int N=1e6+5;
const int inf=0x7fffffff;
const double eps=1e-7;
template <typename T> void read(T &x){
	x=0;int f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
	for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0';
	x*=f;
}

int n,k;
int a[N];
int q[N];
int head=1,tail=1;
int main()
{
	read(n),read(k);
	Rep(i,1,n)read(a[i]);
	Rep(i,1,n){
		if(i-q[head]>=k)head++;
		while(head<=tail&&a[i]>=a[q[tail]])tail--;
		q[++tail]=i;
		if(i>=k)printf("%d\n",a[q[head]]);
	}
	return 0;
}

头文件比主程序还长

单调队列经典例题

逛画展

切蛋糕

滑动窗口

求m区间内最小值


单调队列优化DP

单调队列还有一个非常重要的用处就是单调队列优化dp

琪露诺

题目描述
在幻想乡,琪露诺是以笨蛋闻名的冰之妖精。

某一天,琪露诺又在玩速冻青蛙,就是用冰把青蛙瞬间冻起来。但是这只青蛙比以往的要聪明许多,在琪露诺来之前就已经跑到了河的对岸。于是琪露诺决定到河岸去追青蛙。

小河可以看作一列格子依次编号为0到N,琪露诺只能从编号小的格子移动到编号大的格子。而且琪露诺按照一种特殊的方式进行移动,当她在格子i时,她只移动到区间[i+l,i+r]中的任意一格。你问为什么她这么移动,这还不简单,因为她是笨蛋啊。

每一个格子都有一个冰冻指数A[i],编号为0的格子冰冻指数为0。当琪露诺停留在那一格时就可以得到那一格的冰冻指数A[i]。琪露诺希望能够在到达对岸时,获取最大的冰冻指数,这样她才能狠狠地教训那只青蛙。

但是由于她实在是太笨了,所以她决定拜托你帮它决定怎样前进。

开始时,琪露诺在编号0的格子上,只要她下一步的位置编号大于N就算到达对岸。

输入格式
第1行:3个正整数N, L, R

第2行:N+1个整数,第i个数表示编号为i-1的格子的冰冻指数A[i-1]

输出格式
一个整数,表示最大冰冻指数。保证不超过2^31-1

说明/提示
对于100%的数据:N <= 200,000

对于所有数据 -1,000 <= A[i] <= 1,000且1 <= L <= R <= N


这道题很容易想到是一个DP,但是一个 O ( N 2 ) O(N^2) 的DP肯定是要炸时间的

所以我们就要用到单调队列了,因为每一个点只会入队和出队最多一次,所以这个单调队列优化的DP是 O ( N ) O(N)

首先,必须要从L开始,这个时候我们用一个now来维护当前所能达到i的最末尾的,然后就是滑动窗口了,在此不赘述了。

注意循环要从L开始

代码:

# include <cstdio>
# include <algorithm>
# include <cstring>
# include <queue>
# include <climits>
# define Rep(i,a,b) for(int i=a;i<=b;i++)
# define _Rep(i,a,b) for(int i=a;i>=b;i--)

using namespace std;

const int N=2e5+5;

int n,l,r,ans=INT_MIN;
int a[N],f[N];
int q[N];
int head=1,tail=1;

int main()
{
	scanf("%d%d%d",&n,&l,&r);
	Rep(i,0,n)scanf("%d",&a[i]);
	memset(f,0xcf,sizeof(f));
	f[0]=0;
	int now=0;
	Rep(i,l,n){
		while(head<=tail&&f[q[tail]]<=f[now])tail--;
		q[++tail]=now;
		while(q[head]+r<i)head++;
		f[i]=f[q[head]]+a[i];
		now++;
	}
	Rep(i,n-r+1,n)ans=max(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}
发布了18 篇原创文章 · 获赞 3 · 访问量 1261

猜你喜欢

转载自blog.csdn.net/devout_/article/details/100164519
今日推荐