数据结构基础【单调队列】

单调队列

单调队列一般是具有单调性的队列,单调队列有单调递增和单调递减两种,一般来讲,队列的队首是整个队列的最大值或最小值,它的思想也是在决策集合(队列)中及时排除一定不是最优解的选择。单调队列是优化动态规划的一个重要手段。

具体实现步骤:
维护单调单调递增队列:

  1. 若队列为空,将S[i]从队尾入队。
  2. 若队列不为空,将比S[i]大的元素都从队尾弹出,然后把S[i]入队。
  3. 若队列不为空且S[i]大于队尾,则直接从队尾把S[i]入队。

例题
最大子序和 CH1201

题目大意:
给定一个长度为N的整数序列(可能有负数),从中找出一段长度不超过M的连续子序列,使得子序列中所有数的和最大。N,M≤3*105。

思路:
计算“区间和”的问题,一般转化为“两个前缀和相减”的形式进行求解。我们先求出S[i] 表示序列里前i项的和,则连续子序列[L,R] 中数的和就等于S[R ]- S[L-1]。那么原问题可以转化为:找出两个位置x,y, 使S[y] - S[x] 最大并且y - x <= M。
首先我们枚举右端点i,当i固定时,问题就变为:找到一一个左端点」,其中j属于[i - m, i - 1]并且S[j]最小。
比较一下任意两个位置 j 和 k ,如果 k < j < i 并且S[k] ≥ S[j],那么对于所有大于等于i的右端点,k永远不会成为最优选择。这是因为不但S[k] 不小于s[j].而且j离i更近,长度更不容易超过M,即j的生存能力比k更强。所以当j出现后, k 就完全是一个无用的位置。
可以得出结论:
可能成为最优选择的策略集合一定是一个“下标位置递增、对应的前缀和s的值也递增”的序列。我们可以用一个队列保存这个序列。

对每个 i 进行下面3个操作:

  1. 判断队头决策与i的距离是否超出M的范围,若超出则出队。
  2. .此时队头就是右端点为i时,左端点j的最优选择。
  3. 不断删除队尾决策,直到队尾对应的s值小于s[i]。 然后把i作为一个新的决策入队。

代码:

#include<iostream>
#include<cstring>
#include<limits.h>
using namespace std;
typedef long long ll;
const int N = 300010;
ll sum[N];
int q[N];
int n, m, a;
int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        scanf("%d", &a);
        sum[i] = a + sum[i - 1];
    }
    int l = 0, r = 0;
    ll ans = INT_MIN;
    for(int i = 1; i <= n; i++){
        if(q[l] < i - m) l++;
        ans = max(ans, sum[i] - sum[q[l]]);
        while(l <= r && sum[q[r]] > sum[i]) r--;
        q[++r] = i;
    }
    cout << ans << endl;
    return 0;
}

发布了28 篇原创文章 · 获赞 22 · 访问量 1016

猜你喜欢

转载自blog.csdn.net/qq_45432665/article/details/104038808