单调队列原理+练习

时光怂恿深爱的人放手 提交于 2020-01-30 21:55:34

很早之前就学过了,现在系统的再学习下,做个汇总。

原理


deque实现,队头存最大值/最小值,从队头到队尾使之单调减/增。
以队头存最小值为例:

  • 队列为空,直接入队
  • 依次将队列中大于(等于也可以)当前值的数从队尾出队,最后再将当前值入队尾
deque<int> q;
for(int i = 0; i < n; i++)
{
	 while(!q.empty() && q.back()>a[i]) q.pop_back();
     q.push_back(a[i]);
}

练习


  • 洛谷P1886-滑动窗口-模版题
    传送门
    解题思路: 因为有长度k的限制,所以队列中存的是值的下标。在输出结果时要保证队头的元素在[i-k+1,i]内,即将队列中不再该范围内的值依次从队头取出,再输出当前的最大/小值。
    ac代码:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 1e6+10;
int a[maxn];
deque<int> q;//队头最大/小值
int n, k;
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    cin >> n >> k;
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    for(int i = 0; i < n; i++)//队头存最小值
    {
        while(!q.empty() && a[q.back()]>a[i]) q.pop_back();
        q.push_back(i);
        if(i>=k-1)
        {
            while(!q.empty() && q.front()<=(i-k)) q.pop_front();//除去不在范围内的最小值
            printf("%d ",a[q.front()]);
        }
    }
    printf("\n");
    q.clear();
    for(int i = 0; i < n; i++)//队头存最大值
    {
        while(!q.empty() && a[q.back()]<a[i]) q.pop_back();
        q.push_back(i);
        if(i>=k-1)
        {
            while(!q.empty() && q.front()<=(i-k)) q.pop_front();
            printf("%d ", a[q.front()]);
        }
    }
    return 0;
}
  • 牛客1006D-最大子序列和
    传送门
    题目: 给定长度为n的整数序列,找到最大的长度不超过m的连续子序列和,n,m≤3e5
    解题思路: 固定子序列[j, i]的右端点i,那么j[im+1,i]j\in[i-m+1,i]且子序列[j,i]内的和利用前缀和可以转化为sum[i]sum[j1]sum[i]-sum[j-1]。i一定,sum[j-1]最小,连续子序列和最大。令J=j-1,J[im,i1]J\in[i-m, i-1]。在枚举i的过程中,J的区间每次右移一位,每次都在相应的区间内找最小值,这样这道题就转化成了洛谷P1886滑动窗口那道题,用单调队列来做,队头存最小值。
    注意队列里要先放一个0,因为i是先取到最小值(最小值是在队列非空的时候才取得到)后再存入。
    ac代码:
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn = 3e5+10;
deque<int> q;//队头最大/小值
int n, m;
ll sum[maxn];
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%lld", &sum[i]), sum[i]+=sum[i-1];
    ll ans = LLONG_MIN;
    //在[i-m, i-1]中找最小的sum[J]
    q.push_back(0);
    for(int i = 1; i <= n; i++)
    {
        while(!q.empty() && sum[q.back()]>sum[i]) q.pop_back();
        while(!q.empty() && q.front()<i-m) q.pop_front();
        if(!q.empty()) ans = max(ans, sum[i]-sum[q.front()]);
        q.push_back(i);
    }
    printf("%lld\n", ans);
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!