单调队列

单调队列,顾名思义就是一个具有单调性的一个队列,可是该怎么实现呢。

用普通的队列肯定不能实现,因此我们需要用到里一个数据结构——双端队列,这个也比较容易理解,就是两头都可以进和出队的操作。

然后我们就可以进行愉快的写单调队列了。

单调队列与优先队列还不一样,优先队列只要你不主要删除,他是不会删的,但是单调队列不一样,只要不符合单调性,那先让不满足性质的数先出来,然后再加入这个不符合单调性的毒瘤。

举个例子

比如我们现在需要一个单调递增的队列,(注意这里的单调递增不是指队列里的值按照顺序单调递增,而是从队尾出队的时候要单调递增)

比如下面这个单调递增的队列:

5 3 2 1

接下来要从队尾添加一个数

分两种情况:

1:

  • 然后如果我们加入了一个符合单调性的数,直接加就好了。
  • 代码:
    if(data[i] < q.back())
        q.push_back(data[i]);

2:

  • 反之,因为我们要使队列满足单调性,所以要先让不满足的数出队。
  • 代码:
  • while(!q.empty() && data[i] > q.back())
        q.pop_back();

    然后将这个大毒瘤入队

  • 代码:
    q.push_back(data[i]);

    这基本上就是单调队列的基本操作了,但是要灵活掌握,比如如果像是滑动窗口那样的题,可以入队编号,然后再通过编号来判断是否该编号在不在窗口里。

 例题:

洛谷P2639

我们先分析一下这个题目,看上去可能就是一个需要优化的枚举罢了,但是该怎么枚举呢,我们就需要先简化一下题意。

就是我们把这n个点看最多有哪些点破环成链之后所有位置的前缀和都非负,可是这个又与单调队列什么关系呢,所谓破环成链,便是找到一个点,设这个点的位置为pos,则把1——pos的所有值都移到n + 1——n + pos +1上,这样就和题意相符了,这样还不够,因为我们还需要在规定的时间内完成任务,这就很尴尬了,就需要一个数据结构来加快速度,这里我们选用单调队列,因为我们只要判断前缀和的最小值减去sum[k-1]非负就好了。因为只要有一个点是负的,这个k就不行,就可以枚举下一个k了。

最终思路:

  1. 首先我们应该破环成链。
  2. 我们先预处理出所有前缀和。

    3:然后对于每个点k,我们判断每个点的最小值与sum[k - 1]的关系。并统计个数。 

代码:

#include <iostream>
#include <cstdio>
#include <queue>
#define M 1000010

using namespace std;

struct cym {
    int val, pos;
}e[M];
int sum[M], n, minn, tot;
deque <cym> q;
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int x;
        scanf("%d", &x);
        sum[i] = sum[i - 1] + x;
        while(!q.empty() && q.back().val > sum[i])
            q.pop_back();
        q.push_back((cym){sum[i], i});
    }
    for(int i = 1; i <= n; i++)
    {
        if(q.front().pos < i)
            q.pop_front();
        if(q.front().val < sum[i - 1])
        continue;
        minn = min(minn, sum[i - 1]);
        if(minn < sum[i - 1] - sum[n])
        continue;
        tot++;
    }
    printf("%d", tot);
    return 0;
}
原文地址:https://www.cnblogs.com/liuwenyao/p/9416344.html