目录
1、STL库中的队列
1、常用函数
q.size();返回q里元素个数
q.empty();返回q是否为空,空则返回true
q.push(k);在q的末尾插入k
q.pop();删除q的第一个元素
q.top();查询q的第一个元素q.clear();清空q队列——(队列不支持clear函数,只有双端队列支持clear()函数)
q.push_back();向双端队列的最后插入一个数
q.push_front();向双端队列的头部插入一个数
q.pop_back();将双端队列的最后一个数删掉q.pop_front();将双端队列的第一个数删掉
1、priority_queue优先队列:是一种能根据元素优先级进行自动排序的队列。
- priority_queue<int> q;队列这里默认为从大到小排列
- priority_queue<int , vector<int>,greater<int> > q1;这里用的是小根堆,所以队列是从小到大排列的
- priority_queue<int , vector<int>,less<int> > q2;这里用的是大根堆,所以队列是从大到小排列的
2、优先队列 ——(卡牌游戏)
题目描述:
一副牌卡片(n张),每一张都有其力量的特点。有两种类型的卡片:
1:一张英雄牌,这样一张牌的力量永远等于0;
2:一张奖励卡,这种卡的力量总是积极的。你可以对牌组做以下操作:
从牌堆顶端取一张牌,如果这张卡是奖励卡,你可以把它放在你的奖励卡组顶部或丢弃;
如果这张牌是英雄牌,那么你的奖励牌组中最上面那张牌的能量会被加到他的能量中(如果它不是空的),之后英雄会被加到你的军队中,使用的奖励会被丢弃。
你的任务是使用这些行动聚集一支军队与最大可能的总力量。输入:
第一行输入数据包含单个整数t(1≤t≤1e4)测试中的测试用例的数量。
每个测试用例的第一行包含一个整数n(1≤n≤2e5) 一副牌中的牌数。
每个测试用例的第二行包含n整数s1, s2,…,sn(0≤si≤1e9) 卡片权力自上而下的顺序。
它保证n的和对于所有测试用例不超过2e5。输出:
输出一个数字,每个数字都是对应测试用例的答案——军队可以达到的最大可能的总力量。样例输入:
5 5 3 3 3 0 0 6 0 3 3 0 0 3 7 1 2 3 0 4 5 0 7 1 2 5 0 4 3 0 5 3 1 0 0 4
样例输出:
6 6 8 9 4
请注意:
在第一个例子中,你可以拿奖金1和2两张英雄卡都将收到3张权力。如果你把所有的奖金都拿走了,其中有一个奖金将被闲置。
在第二个例子中,桥牌顶部的英雄卡不能被加能,其余的卡可以用2加能和3获得6个奖励总力量。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
LL m, n, a[N];
int main()
{
cin >> m;
while (m--)
{
cin >> n;
priority_queue<int>q;
for (int i = 1; i <= n; i++) cin >> a[i];
LL res = 0;
for (int i = 1; i <= n; i++)
{
if (a[i] != 0) q.push(a[i]);
if (a[i] == 0 && q.size() > 0)
{
int x = q.top();
res += x;
q.pop();
}
}
cout << res << endl;
}
return 0;
}
3、单调&&双端队列——(最大子序列和)
题目描述
输入一个长度为 n 的整数序列,从中找出一段长度不超过 m 的连续子序列,使得子序列中所有数的和最大。
注意: 子序列的长度至少是 1。
输入格式
第一行输入两个整数 n,m。第二行输入 n 个数,代表长度为 n 的整数序列。
同一行数之间用空格隔开。
输出格式
输出一个整数,代表该序列的最大子序和。数据范围
1≤n,m≤300000
输入样例:6 4 1 -3 5 1 -2 3
输出样例:7
4、单调&&双端对列——(滑动窗口)
题目描述:
给定一个大小为 n≤1e6 的数组
有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边
你只能在窗口中看到 k 个数字
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为
[1 3 -1 -3 5 3 6 7]
,k 为 3;
你的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。该数组为
[1 3 -1 -3 5 3 6 7]
,kk 为 33。
窗口位置 最小值 最大值 [1 3 -1] -3 5 3 6 7 -1 3 1 [3 -1 -3] 5 3 6 7 -3 3 1 3 [-1 -3 5] 3 6 7 -3 5 1 3 -1 [-3 5 3] 6 7 -3 5 1 3 -1 -3 [5 3 6] 7 3 6 1 3 -1 -3 5 [3 6 7] 3 7 输入格式:
输入包含两行
第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度
第二行有 n 个整数,代表数组的具体数值
同行数据之间用空格隔开。
输出格式
输出包含两个
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。
输入样例:
8 3 1 3 -1 -3 5 3 6 7
输出样例:
-1 -3 -3 -3 3 3 3 3 5 5 6 7
详细流程(以输出最小值为例):
q用来存放数组的下标,a用来存放队列中的值;(队列从0开始)
1、STL库中的双端对列
#include<iostream>//单调队列
#include<algorithm>
#include<cstring>
#include<deque>
using namespace std;
const int N = 1e6 + 10;
int n, k, a[N];
int main()
{
deque<int>q;//双端队列
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++)//搞最小
{
while (q.size() && q.back() > a[i]) q.pop_back();//新进入窗口的值小于队尾元素,则队尾出队列
q.push_back(a[i]);
if (i - k >= 1 && q.front() == a[i - k]) q.pop_front();//判断队头是否滑出了窗口,队头出队
if (i - k >= 0) cout << q.front() << " ";//当窗口形成,输出队头对应的值
}
q.clear();
cout << endl;
for (int i = 1; i <= n; i++)//搞最大
{
while (q.size() && q.back() < a[i]) q.pop_back();
q.push_back(a[i]);
if (i - k >= 1 && q.front() == a[i - k]) q.pop_front();
if (i - k >= 0) cout << q.front() << " ";
}
return 0;
}
2、手写队列
- 刚开始队列中没有一个元素,直接让第一个元素1(下标也为1)进队,此时a={1},q={0}。
- 然后我们考虑下一个数字3能不能放进去,答案是可以放,因为我们将3放进去如果后面两个数字都比3大,那么它很有可能在有生之年成为最小的值,此时a={1,3},q={0,1}。
- 然后我们考虑下一个数字-1能不能放进去,队尾元素3比-1大,那么意味着只要让-1进队,那么3在其有生之年永远成为不了最小值。因为在窗口移动过程中,只要3被框起来,那么-1也就一定被框起来,因此3也就永远不可能成为最小值了,所以3从对尾出队列。
- 接下来轮到1,同理只要有-1在,1也就永远不可能成为最小值了;1也出对列,最后让-1进队,此时a={-1},q={2};(现在窗口也已经满足了k=3的条件(i-k+1>=0)输出对头元素,也就是输出-1。)
- 接下来轮到了-3,同理第三步分析-1>-3,只要有-3在,-1也就永远不可能成为最小值了,-1从队尾出队列,-3从队尾进队列,此时a={-3},q={3};(现在窗口也已经满足了k=3的条件(i-k+1>=0)输出对头元素,也就是输出-3。)
- 接下来轮到了5,同理第二步分析,5在有生之年还是有可能成为最小值的,所以5进队列,此时,a={-3,5},q={3,4};(现在窗口也已经满足了k=3的条件(i-k+1>=0)输出对头元素,也就是输出-3。)
- 接下来轮到了3,同理第三步分析3<5,只要有3在,5也就永远不可能成为最小值了,5从队尾出队列,3从队尾进队列,此时a={-3,3},q={3,5};(现在窗口也已经满足了k=3的条件(i-k+1>=0)输出对头元素,也就是输出-3。)
- 接下来轮到了6,同第二步分析,6在有生之年还是有可能成为最小值的,所以6进队,此时的-3已经从窗口中滑出来(i-k+1>a[hh])从对首出队列,此时,a={3,6},q={5,6};(现在窗口也已经满足了k=3的条件(i-k+1>=0)输出对头元素,也就是输出3。)
- 接下来轮到了7,同第二步分析,7在有生之年还是有可能成为最小值的,所以7进队列,此时,a={3,6,7},q={5,6,7};(现在窗口也已经满足了k=3的条件(i-k+1>=0)输出对头元素,也就是输出3。)
1、经过操作之后,队列中元素大小递增,因此每次只需要输出队列头部即可;
2、时间复杂度O(n);
3、输出窗口最大值同理即可(改变一下符号);
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int n, k, a[N], tt, hh, q[N];//q数组用来存放下标
int main()
{
scanf("%d %d", &n, &k);
tt = -1, hh = 0;//初始化队尾和对头
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < n; i++)
{
if (hh<=tt && i - k + 1 > q[hh]) hh++;//移动窗口最左端,维持窗口大小
while (hh <= tt && a[i] <= a[q[tt]]) tt--;//构造单调递增队列
//当队列不为空当队列队尾元素>=当前元素(a[i])时,那么队尾元素一定不是当前窗口最小值
//就删去队尾元素,加入当前元素(q[ ++ tt] = i)
q[++tt] = i;
if (i - k + 1 >= 0) printf("%d ", a[q[hh]]);//输出最小值
}
//同理输出最大值也是一样的;
tt = -1, hh = 0;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
for (int i = 0; i < n; i++)
{
if (hh <= tt && i - k + 1 > q[hh]) hh++;
while (hh <= tt && a[i] >= a[q[tt]]) tt--;
q[++tt] = i;
if (i - k + 1 >= 0) printf("%d ", a[q[hh]]);
}
return 0;
}