单调队列的简单总结


前言

主要是讲解一下单调队列与优先队列的用处,以及二者之间的差异。


一、单调队列

什么是单调队列,正如其名,在该队列中单调。
一般单调队列,我更倾向于利用双端队列来解决。
在这里插入图片描述

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.为什么单调队列不会内存超限

在这里插入图片描述

总结

当然本篇主要是对于单调队列进行讲解的,其中的优先队列,会出现用法的相同,以此才会提一下,总的来说,单调队列就是主要解决数据流中的最值问题,同时还可以解决滑动窗口问题,其次还可以优化动态规划问题,只不过目前还没有对动态规划进行深入,所以这方面的点就没有进行提及,等到学习的更深入时,将会把这篇文章进行完善。
最后再给自己提个劲:正因为没有翅膀,人们才会寻找飞翔的方法-----<<排球少年>>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值