STL 中队列的使用(queue)
一.基本操作:
push(x) 将x压入队列的末端 //不能push_back
pop() 弹出队列的第一个元素(队顶元素),注意此函数并不返回任何值
front() 返回第一个元素(队顶元素)
back() 返回最后被压入的元素(队尾元素)
empty() 当队列为空时,返回true
size() 返回队列的长度
头文件:#include <queue>
声明方法: 1、普通声明 queue<int>q;
2、结构体 struct node{ int x, y; }; queue<node>q;
二.STL 中优先队列的使用方法(priority_queue)
优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素。
但是它有一个特性,就是队列中最大的元素总是位于队首,
所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队。
这点类似于给队列里的元素进行了由大互小的顺序排序。
元素的比较规则默认按元素值由大到小排序,可以重载“<”操作符来重新定义比较规则。
基本操作:empty() 如果队列为空返回真
pop() 删除对顶元素 push() 加入一个元素
top() 返回优先队列对顶元素
在默认的优先队列中,优先级高的先出队。
在默认的int型中先出队的为较大的数。
头文件:#include <queue>
1.普通方法:priority_queue<int>q; //通过操作,按照元素从大到小的顺序出队
priority_queue<int,vector<int>,greater<int> >q;
//通过操作,按照元素从小到大的顺序出队
2、自定义优先级: priority_queue<int, vector<int>, cmp>q; //定义方法
//其中,第二个参数为容器类型。第三个参数为比较函数。
单调队列
即所有在队列里的数都必须按递增(或递减)的顺序列队的队列。
用法:在决策队列中,及时排除一定不是最优解的选择。
【例题1】(来源:caioj 1172)[ 子段最大值---单调递减队列 ]
给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。
解法1*** 如果按照常规方法,我们在求f[i]即i~i+m-1区间内的最值时,
要把区间内的所有数都访问一遍,时间复杂度约为O(nm)。
解法2*** 上个算法找当前的f(i)的时候,i的前面k-1个数其它在算f(i-1)的时候我们就比较过了。
保存上一次的结果?当然主要是保存i的前k-1个数中的最大值了。要用到单调递减队列。
使用单调队列就涉及到去头和删尾***
1、队列的头一定是在一段时间前就加入了队列,现在的队列头会不会离开了我们处理的区间呢?
如果它离我们正在处理的i太远了,我们就要把它去掉,去除冗杂的信息。
2、为了保证队列的递减性,在从列队尾新插入元素v时,要考虑队列尾的值是否大于v,
如果是,队列呈现 队列尾-1的值 > 队列尾的值 > v ,此时队列递减性没有消失;
如果不是,队列呈现 队列尾-1的值 > 队列尾的值 < v ,队列递减性被打破。
为了维护递减性,我们做如下考虑:v是最新值,它的位置是目前最靠后的,
它可成为以后的最大值,必须留下;队列尾-1的值与v大小不定,不能冒然删去它;
队列尾的值夹在v和队列尾-1之间,它不但不是最大值,对于以后的情况又不如v优,
因为v相比队列尾更靠后(v可以影响到后m个值,队列尾只能影响到从它的位置往后数m-1个值),
而且值更大,所以删队列尾是必定的。
の 在维护好一个 区间正确、严格递减 的单调递减队列后,队列头就是当前区间的最大值了。
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
/*【caioj 1172】单调队列找max
给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。 */
//队列中存之后有可能成为最大值的数,每次入队时,清除不可能的队头和队尾。
struct node{
int x,p; //价值 和 位置
}list[201000];
int a[201000];
int main(){
int n,m; scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int head=1,tail=1; //手写优先队列
list[1].x=a[1]; list[1].p=1; //初始点为1
for(int i=2;i<=n;i++){
while(head<=tail && list[tail].x<=a[i]) tail--;
//↑↑新数比前几个大,前几个不可能再成为最大值(可能不止一个)
tail++; list[tail].x=a[i]; list[tail].p=i; //a[i]加入队尾
while(list[head].p<i-m+1) head++; //p的作用:判断区间长度
if(i>=m) printf("%d\n",list[head].x); //每一次的队头都是当前段最大值
//单调递减队列:如果后方有数更大,前面就删除;所以队列一定单调递减
}
return 0;
}
【例题2】caioj 1173
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
/*【caioj 1173】单调队列 [减少继承的状态]
有N个数(N<=100000),在连续M(M<=N)个数里至少要有一个数被选择。
求选出来数的最小总和。 */
//队列中存之后有可能成为最大值的数,每次入队时,清除不可能的队头和队尾。
struct node{
int x,p; //价值 和 位置
}list[201000];
int a[201000],f[201000]; //f[i]选择i,且保证1~i符合题意的最小价值
int main(){
int n,m; scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int head=1,tail=1;
list[1].x=0; list[1].p=0; //继承0位置
for(int i=1;i<=n;i++){
while(head<=tail && i-list[head].p>m) head++; //淘汰超出范围的队头
f[i]=list[head].x+a[i]; //选择遇到的第一个不能删的队头,可以管理的后面的数最多
while(head<=tail && list[tail].x>f[i]) tail--; //list中是升序序列
tail++; list[tail].x=f[i]; list[tail].p=i;
}
int ans=(1<<30);
for(int i=n-m+1;i<=n;i++) //最后一段,任选f[i]都能保证满足
if(f[i]<ans) ans=f[i];
printf("%d",ans);
return 0;
}
【例题3】最大子序和 tyvj - 1305
#include <cmath>
#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
typedef long long ll;
/*【最大子序和】单调队列
输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。 */
//先求出前缀和s[i],枚举右端点i,寻找最小的s[j]对应的左端点
//最优策略集合一定是“下标位置递增,对应前缀和s也递增”的序列
//1.判断队头决策和i的距离是否超过m的范围;
//2.此时队头是右端点为i,左端点为j的最优选择;
//3.不断删除队尾决策,直到队尾对应的s值小于s[i],使i入队。
//由此,队列变成了 关于前缀和的 单调递增序列↑↑↑↑↑
/*【分析】维护一个{ 下标递增且前缀和递增 }的队列。
每一次入队一个i,就删除在i前面所有不满足“单调性”的元素,
即当i入队后,这些元素一定不如i,在i的存在下这些元素一定不会被选入最优答案集合中,
也就是类似sj<=si且j<i的元素。删除的时候直接让队尾出队即可。 */
const int MAXN = 300000 + 10;
int q[MAXN],a[MAXN],s[MAXN];
int main() {
int n,m,ans; scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%d",a[i]);
s[i]=a[i]+s[i-1]; //前缀和
}
int l=1,r=1;
for(int i=1;i<=n;i++) { //先枚举来固定右端点,问题转化为枚举左端点
while(l<=r&&q[l]<i-m) l++; //判断队头决策和i的距离是否超过m的范围
ans=max(ans,s[i]-s[q[l]]); //此时队头q[l]是右端点为i的最优左端点选择
while(l<=r&&s[q[r]]>=s[i]) r--; //核心,及时弹出[对答案贡献一定不如i]的元素
q[++r]=i;
}
printf("%d", ans);
return 0;
}
整理归纳单调队列的定义:
1、维护区间最值;
2、去除冗杂状态;
3、保持队列单调(最大值是单调递减序列,最小值是单调递增序列);
4、最优选择在队首。
整理归纳单调队列的使用方法:
1、维护队首(对于上题就是如果你已经是当前的m个之前那你就可以被删了) ;
2、在队尾插入(每插入一个就要从队尾开始往前去除冗杂状态) ;
3、取出需要的最优解(队列头的值即是);
4、借助最优解,得到目前所求的最优解(通常此处插入DP方程)。
单调队列的原理:
在处理f[i]时,去除冗杂、多余的状态,使得每个状态在队列中只会出现一次;
同时维护一个能瞬间得出最优解的队列,减少重新访问的时间;
在取得自己所需的值后,为后续的求解做好准备。