文章目录
前言
主要是讲解一下单调队列与优先队列的用处,以及二者之间的差异。
一、单调队列
什么是单调队列,正如其名,在该队列中单调。
一般单调队列,我更倾向于利用双端队列来解决。
1.单调队列的核心操作(以单调递减队列为例)
代码体现:
while (!dq.empty() && a[dq.back()] <= x) {
dq.pop_back(); // 删除破坏单调性的队尾元素
}
dq.push_back(i); // 新元素入队
代码体现:
while (!dq.empty() && dq.front() <= i - k) {
dq.pop_front(); // 移除窗口外的队首元素
}
2.例题实战
P1886 滑动窗口 /【模板】单调队列
题目传送门:P1886 滑动窗口 /【模板】单调队列
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll MAX = 1e6 + 1;
ll m[MAX];
signed main() {
ll n, k;
cin >> n >> k;
deque<ll> p;
for (ll i = 1; i <= n; i++) {
cin >> m[i];
}
for (ll i = 1; i <= n; i++)//求区间最小值
{
if (!p.empty() && p.front() <= i - k) {
p.pop_front();
}
while (!p.empty() && m[i] <= m[p.back()])//单调递增队列
{
p.pop_back();
}
p.push_back(i);
if (i >= k) {
cout << m[p.front()] << " ";
}
}
cout << endl;
p.clear();
for (ll i = 1; i <= n; i++)//求区间最大值
{
if (!p.empty() && p.front() <= i - k) {
p.pop_front();
}
while (!p.empty() && m[i] >= m[p.back()])//单调递减队列
{
p.pop_back();
}
p.push_back(i);
if (i >= k) {
cout << m[p.front()] << " ";
}
}
cout << endl;
return 0;
}
P1714 切蛋糕
题目传送门:P1714 切蛋糕
解题思路:通过题目可以看见关键词语也就最多,可知不一定是选择固定的区间,由第二个例子可以知道
同时,题目也提示到了前缀和,通过前缀和与单调队列可以解决。
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
const ll N=1e6+1;
ll s[N]; // 前缀和数组
deque<ll> p; // 单调队列,存前缀和下标
signed main() {
IOS;
ll n, m;
ll ans = -1e18; // 初始化为极小值
cin >> n >> m;
for (ll i=1; i<=n; ++i) {
ll a;
cin >> a;
s[i] = s[i-1] + a; // 计算前缀和
}
p.push_back(0); // 初始化队列,s[0] = 0(对应子数组从1开始的情况)
for (ll i=1; i<=n; ++i) {
// 1. 维护队列窗口:移除超出范围的队首(保证l >= i-m+1 → l-1 >= i-m → 下标是l-1)
while (!p.empty() && p.front() < i - m) {
p.pop_front();
}
// 2. 计算当前i对应的最大子数组和:s[i] - s[队首](队首是最小s[l-1])
ans = max(ans, s[i] - s[p.front()]);
// 3. 维护队列单调性:移除队尾 >= 当前s[i]的下标(保证队列递增)
while (!p.empty() && s[p.back()] >= s[i]) {
p.pop_back();
}
p.push_back(i); // 当前下标入队
}
cout << ans << endl;
return 0;
}
经过这两个题目差不多可以理解一点,剩下的可以多去刷一些类似的题目,主要是理解其中的逻辑
二、优先队列
当然这一篇的优先队列,主要是对于滑动窗口的解决,其更深层次还未理解。
1.优先队列的概念:
2.语法与定义
默认最大堆:
#include <queue>
using namespace std;
// 存储 int 类型,默认最大堆,底层用 vector 实现(也可用 deque)
priority_queue<int> max_heap;
最小堆:
#include <queue>
#include <vector>
using namespace std;
// 存储 int 类型,最小堆,底层用 vector 实现
priority_queue<int, vector<int>, greater<int>> min_heap;
3.例题
P1886 滑动窗口 /【模板】单调队列
题目传送门:P1886 滑动窗口 /【模板】单调队列
相对于上面的单调队列,还可以利用优先队列来进行解决这一问:
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
const int N = 1e6 + 10;
ll a[N];
int n, k;
// 求滑动窗口最小值
void getMin() {
deque<int> dq; // 存储下标,对应值单调递增
for (int i=1; i<=n; ++i) {
// 移除窗口外的队首元素
while (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
// 移除队尾 >= 当前值的元素,维护单调递增
while (!dq.empty() && a[dq.back()] >= a[i]) {
dq.pop_back();
}
dq.push_back(i);
// 窗口形成后输出
if (i >= k) {
cout << a[dq.front()] << " ";
}
}
cout << "\n";
}
// 求滑动窗口最大值
void getMax() {
deque<int> dq; // 存储下标,对应值单调递减
for (int i=1; i<=n; ++i) {
// 移除窗口外的队首元素
while (!dq.empty() && dq.front() < i - k + 1) {
dq.pop_front();
}
// 移除队尾 <= 当前值的元素,维护单调递减
while (!dq.empty() && a[dq.back()] <= a[i]) {
dq.pop_back();
}
dq.push_back(i);
// 窗口形成后输出
if (i >= k) {
cout << a[dq.front()] << " ";
}
}
cout << "\n";
}
signed main() {
IOS;
cin >> n >> k;
for (int i=1; i<=n; ++i) {
cin >> a[i];
}
getMin();
getMax();
return 0;
}
注意如果,该数据范围再大一些,会出现内存超限,要谨慎使用。
三,二者的差异
1.为什么单调队列不会内存超限
总结
当然本篇主要是对于单调队列进行讲解的,其中的优先队列,会出现用法的相同,以此才会提一下,总的来说,单调队列就是主要解决数据流中的最值问题,同时还可以解决滑动窗口问题,其次还可以优化动态规划问题,只不过目前还没有对动态规划进行深入,所以这方面的点就没有进行提及,等到学习的更深入时,将会把这篇文章进行完善。
最后再给自己提个劲:正因为没有翅膀,人们才会寻找飞翔的方法-----<<排球少年>>