队列小结——(优先、单调&&双端队列)

目录

1、STL库中的队列

2、优先队列 ——(卡牌游戏)

3、单调&&双端队列——(最大子序列和)

4、单调&&双端对列——(滑动窗口) 

1、STL库中的双端对列

2、手写队列 


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-13
1 [3 -1 -3] 5 3 6 7-33
1 3 [-1 -3 5] 3 6 7-35
1 3 -1 [-3 5 3] 6 7-35
1 3 -1 -3 [5 3 6] 736
1 3 -1 -3 5 [3 6 7]37

输入格式:

输入包含两行

第一行包含两个整数 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;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大小胖虎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值