信息奥赛C++学习笔记——单调队列

单调队列

一.简介
单调队列,顾名思义就是具有单调性的队列,它有如下两个性质
1.队列中的元素大小是递增/递减的(其实也可以自定义)。
2.队列中元素的序号(对应在原序列中的序号)必须是单调递增的,即元素下标递增。
二.作用
1.求解动态区间最大值(比线段树快捷)
2.求解数组中第一个大于/小与某元素x的数
3.优化dp(QWQ然而蒟蒻不会)
1,2会在具体例子中详解。
三.前提知识
单调队列与普通队列的一个差别是:元素既可以从队尾出列,也可以从队首出列,因此需要用双向队列(deque)实现。
deque a;//<>内自定义数据类型;
deque.push_back(k); //在容器尾部添加一个数据
deque.push_front(k); //在容器头部插入一个数据
deque.pop_back(); //删除容器最后一个数据
deque.pop_front(); //删除容器第一个数据
四.作用之——求解动态区间最大/小值
这里以滑动窗口为模板。
算法流程如下(求滑动窗口最小值):
1.若队列为空,将A[i]从队尾入队
2.若队列不为空,将比A[i]大的元素都从队尾弹出,然后把A[i]入队
3.若队列不为空且A[i]大于队尾,则直接从队尾把A[i]入队
4.若队首元素下标不在区间内,从队首弹出
下面描述同理(略显啰嗦可跳过)
维护一个单调递增队列,队列里每一个元素有两个值,下标与大小。保证队首元素(队列最小值)始终作为答案。每当新的元素x入队,比较x与队尾元素y[1]的大小,如果x>y,则x入队;如果x<=y,则y出队,继续比较新的队尾元素y[2]与x的大小…直到x>y[i](或者队列为空),x入队。
如此,保证队列内元素下标递增,大小递增。同时,保证队列中元素下标在区间内,不在区间内的及时从队首出队(deque的用处所在)。

模拟:有 7 6 8 12 9 10 3 七个数字,窗口大小为4,求每个窗口内的最小值。
q为大小队列,p为编号队列。
1.q={ 7 },p={1}//第一个元素入队;
2.q={ 6 }.p={2}// 6比7小,7出,6进;
3.q={6,8},p={2,3}//8比6大,8进;
4.q={6,8,12}, p={2,3,4}//12比8大,12进 窗口区间[1,4];
5.q={6,8,9},p={2,3,5}// 9比12小,12out,9比8大,9进,窗口区间[2,5];
6.q={8,9,10},p={3,5,6}//10比9大,10进,窗口区间[3,6],因为元素6的下标是2,不在[3, 6],中,故6out;
7.q={3},p={7}// 3比单调队列为{ 8,9, 10}的任意值都小,故全out,最终集合为 { 3 };
窗口从4.开始滑动,q的队首元素始终作为答案,故结果为6 6 8 3。

代码如下

#include
using namespace std;
int n,m,a[1000001];
struct node{
    int t,s; //存储下标和大小
}r;
deque<node>q1;
deque<node>q2;
int min_deque(){
    for(int i=1;i<=n;i++){
    	r.t=i,r.s=a[i];
		while(!q1.empty()&&a[i]<=q1.back().s)//维护单调递增队列 
			q1.pop_back();
		q1.push_back(r);
		while(q1.front().t<=i-m)
			q1.pop_front();
		if(i>=m) printf("%d ",q1.front().s);
	}
}
int max_deque(){
    for(int i=1;i<=n;i++){
    	r.t=i,r.s=a[i];
		while(!q2.empty()&&a[i]>=q2.back().s)//维护单调递减队列 
			q2.pop_back();
		q2.push_back(r);
		while(q2.front().t<=i-m)
			q2.pop_front();
		if(i>=m) printf("%d ",q2.front().s);
	}
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    min_deque();
    cout<<endl;
    max_deque();
    return 0;
}

五作用之——求第一个大于/小于元素x的数
这里以P2947向右看齐为例
题目大意即给定一排奶牛的身高,求每个奶牛向右看齐时找到的第一个大于她身高的奶牛的编号
算法流程如下:
维护一个单调递减队列
1.若队列为空,将A[i]从队尾入队
2.若队列不为空且A[i]大于队尾,则直接从队尾把A[i]入队
3.若队列不为空,将比A[i]大的元素都从队尾弹出,然后把A[i]入队,并且标记所有弹出的元素仰望对象为A[i]

代码如下:

#include
using namespace std;
int n,b[100086];
struct qing{
	int s,No;
}a[100086];
deque<qing>q;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i].s);
		a[i].No=i;
	}
	for(int i=1;i<=n;i++){
		while(!q.empty()&&a[i].s>q.back().s){
			b[q.back().No]=a[i].No;
			q.pop_back();
		}
		q.push_back(a[i]);
	}
	for(int i=1;i<=n;i++){
		printf("%d\n",b[i]);
	}
	return 0;
}

你可能感兴趣的:(c++,学习,笔记)