浅谈单调队列的应用

前言:单调队列,就是一个符合单调性质的队列,它同时具有单调的性质以及队列的性质。他在编程中使用频率不高,但却占有至关重要的地位。它的作用很简单,就是为了维护一组单调数据,让我们在运行的过程中能够快速寻求前k个或后k个中最大或最小的值。

例一(ssl 2521):
给出一个长度为n(1<=n<=100000)的整数序列,选择长度不超过k(1<=k<=n)的段,使得总和最大。

分析
方法一:枚举左端点l和右端点r,然后扫一遍计算总和并选取最大值。时间复杂度O(n^3)
方法二:预处理sum[i]表示前i个数的和,枚举左端点l和右端点r,然后O(1)计算总和并选取最大值。时间复杂度O(n^2)
方法三:仍然是预处理sum[i]表示前i个数的和,只枚举右端点r,问题就转换成了在sum[r-k..r-1]中找一个最小值。那么我们维护一个元素单调递增的队列,每次处理完一个r后把队尾所有不小于sum[r]的元素删掉后把sum[r]放进队尾。那么每次只用把队头中元素在原数组中的下标<r-k的元素删掉,然后选取队头即可得到以r为右端点的最大总和。

例二:
有n(1<=n<=100000)种物品,每种物品都有一个价值v[i]和费用w[i],还有一个数量s[i](1<=s[i]<=1000)。现在有m(1<=m<=1000)元,问最多能获得多少的价值。

分析:
大家:不会QAQ
敌敌:这不就是傻叉多重背包嘛,连我都会做,那还有人不会吗?
大家:……
其实这就是傻叉的多重背包,但会发现用最赤裸裸的方法是过不了的,连二进制优化也都不行。那下面就来介绍一下用单调队列来实现的时间复杂度为O(nm)的多重背包。
先回顾最赤裸裸的多重背包做法。
For (int i=1;i<=n;i++)
For (int j=m;j>=0;j--)
For (int k=1;k<=s[i]&&k*w[i]>=j;k++)
F[j]=min(f[j],f[j-k*w[i]]+v[i]*k);
很显然前两个循环是少不了的,那么我们来想如何去掉第三个循环,从而使时间复杂度变为我们想要的O(nm)。
容易得知对于任意的一个f[j](假设现在第一重循环为i)有可能更新到它的就只有f[j’]j’满足mod w[i]j同余且j’<j),f[j]有可能更新到的就只有f[j’]j’满足mod w[i]j同余且j’>j)
也就是说我们可以把j按照mod w[i]的余数将其分为不同的w[i]个部分,且每个部分都是相互独立的。
那么我们就可以采用以下的方法来对每一部分进行枚举:
for (j = 0; j < w[i]; j++)
    {
        for (k = j; k <= m; k += w[i])
        {
            // 状态转移
        }
    }
那么对于某一部分的一个状态假如是k,问题就转换成了在这一部分的数中找到一个l,满足l<k且(k-l)/w[i]<=s[i]且f[l]+v[i]*(k-l)/w[i]最大,这个东西我们显然可以用单调队列来维护。

练习题 poj 1742,ssl 2599,ssl 2570,hdu 3507
<think>嗯,用户想了解单调队列应用场景和示例算法。首先,我需要回忆一下单调队列的基本概念。根据之前的引用资料,单调队列是一种保持元素单调递增或递减的队列,常用于解决滑动窗口问题。比如引用[4]提到,单调队列可以处理滑动窗口的一系列问题,比如最大值或最小值。 接下来,用户需要具体的应用场景和示例算法。我记得滑动窗口中的最大值是一个典型例子,比如LeetCode中的题目。例如,引用[3]提到了一个子序列的问题,可能涉及窗口内的最大值或最小值。此外,引用[1]提到计算子数组的最小值之和,这可能需要用单调栈,但或许单调队列也能在某些情况下使用,不过需要确认两者的区别。根据引用[1]和[4],单调队列更适合滑动窗口的动态维护,而单调栈可能更适用于静态的问题,比如找到下一个更大元素。 然后,用户可能需要算法实现的例子。比如,滑动窗口最大值可以用单调队列来高效解决,每次窗口移动时维护队列的单调递减性,确保队首是当前窗口的最大值。另外,还有可能的应用如最小子数组和的问题,或者动态规划中的优化,比如在引用[2]中提到的优先队列和堆的关系,但这里需要区分优先队列和单调队列的不同应用场景。 需要确保回答中给出具体的场景,比如滑动窗口最大值、最小值统计、特定条件下的子数组问题等。每个场景最好有一个算法示例,如代码片段,并解释其工作原理。同时,要引用相关的资料,比如引用[4]说明滑动窗口的应用,引用[1]可能关联到子数组的问题,但要注意准确对应。 还要注意用户可能混淆单调队列和单调栈,需要简要说明两者的区别。例如,单调队列支持两端操作,适合动态窗口,而单调栈通常用于一端操作的问题。 最后,生成相关问题时要围绕应用场景、算法实现、与其他数据结构的对比等方面,确保问题能够帮助用户进一步深入理解。</think>### 单调队列应用场景与示例 #### 1. **滑动窗口最大值/最小值** 单调队列最典型的应用是**动态维护滑动窗口内的极值**。例如,给定数组和窗口大小 $k$,要求实时输出窗口滑动时的最大值或最小值。 **示例算法**: ```python def max_sliding_window(nums, k): from collections import deque q = deque() # 存储下标,维护单调递减队列 result = [] for i, num in enumerate(nums): # 移除超出窗口范围的元素 while q and q[0] < i - k + 1: q.popleft() # 维护单调递减性 while q and nums[q[-1]] <= num: q.pop() q.append(i) # 窗口形成后记录最大值 if i >= k - 1: result.append(nums[q[0]]) return result ``` **原理**:通过单调队列在 $O(n)$ 时间复杂度内完成,每次窗口移动时剔除旧元素并保持队列头部为当前最大值[^4]。 --- #### 2. **子数组最小值之和** 题目要求计算所有连续子数组的最小值之和(如引用[1]的问题)。虽然通常用**单调栈**解决,但**单调队列**可优化特定条件下的动态范围操作。 **场景示例**: 若子数组长度被限制为 $\leq k$,则可用单调队列快速筛选最小值的有效范围。 --- #### 3. **动态规划优化** 在动态规划问题中,若状态转移方程涉及某区间内的极值,可用单调队列减少时间复杂度。 **示例**: 给定数组 `nums` 和整数 $k$,求长度不超过 $k$ 的子数组的最大和(如引用[3]的变体)。单调队列可维护候选子数组的起始下标,确保快速找到最优解[^4]。 --- #### 4. **带限制的最短路径** 在图论或序列问题中,若路径需满足某些窗口限制(如步长不超过 $k$),可用单调队列记录候选节点。例如,跳跃游戏变体中,维护可达位置的最小步数。 --- ### 与其他数据结构的对比 | **场景** | **单调队列** | **单调栈** | |-----------------------|-------------------------------------|-------------------------------| | 滑动窗口极值 | ✅ 动态维护,两端操作 | ❌ 无法处理动态窗口 | | 下一个更大元素 | ❌ 需顺序遍历 | ✅ 静态区间,单端操作 | | 区间极值统计 | ✅ 结合双指针或动态规划 | ✅ 静态问题(如柱状图最大矩形) | ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值