单调队列优化DP

单调队列优化DP(解决部分求最值的DP优化)

单调队列

队列中的元素其对应在原来的列表中的顺序必须是单调递增的。

队列中元素的大小必须是单调递(增/减/甚至是自定义也可以)

滑动窗口

有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

我们开两个队列,这两个队列只可以从队首入队,可以从队首和队尾出队。

\(a\) 来存数据,用 \(b\) 来存入队顺序。

例如:

8 3 
1 3 -1 -3 5 3 6 7

假设我们现在在求最小值,我们来模拟一下过程。

1入队,a={1},b={1}

3入队,a={1,3},b={1,2}

-1入队,显然,前面的1和3无论如何都不会成为以i=3为结尾的区间的min,所以弹出1和3

a={-1},b={3}

-3入队,同上,弹出-1

a={-3},b={4}

5入队,-3有可能可以成为min,保留

a={-3,5},b={4,5}

3入队,同第三条,5不可能成为min了,弹出

a={-3,3},b={4,6}

6入队,3有可能成为min,保留。而-3脱离区间,弹出

a={3,6},b={6,7}

7入队,6有可能成为min,保留

a={3,6,7}, b=

过程如下:

  • 如果新数大于等于旧数,保留旧数
  • 如果新数小余旧数,弹出旧数
  • 如果head脱离范围,弹出

每次询问查询a[head]即可

模板

#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m,s[1000005];
ll a[1000005],b[1000005],head,tail;
int main(){	
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>s[i];
	}
	head=1,tail=0;
	for(int i=1;i<=n;i++){//求min 
		while(1){
			if(s[i]<=a[tail]&&tail>=head){//一定是 tail>=head,因为tail可以顶替掉head 
				tail--;
			}
			else{
				break;
			}
		}
		a[++tail]=s[i];
		b[tail]=i;
		while(1){
			if(b[head]<=i-m){
				head++;
			}
			else{
				break;
			}
		}
		if(i>=m){
			cout<<a[head]<<" ";
		}
	}
	cout<<endl;
	head=1,tail=0;
	memset(a,0,sizeof a);
	memset(b,0,sizeof b);
	for(int i=1;i<=n;i++){//求max
		while(1){
			if(s[i]>=a[tail]&&tail>=head){
				tail--;
			}
			else{
				break;
			}
		}
		a[++tail]=s[i];
		b[tail]=i;
		while(1){
			if(b[head]<=i-m){
				head++;
			}
			else{
				break;
			}
		}
		if(i>=m){
			cout<<a[head]<<" ";
		}
	}
} 
posted @ 2025-03-09 21:21  MistyPost  阅读(25)  评论(1)    收藏  举报