跳转至

单调队列

单调队列的作用

单调队列是一种数据结构,它可以维护一个序列中的最大值或最小值。单调队列的特点是队列中的元素从头到尾单调递增或递减。

单调队列的常见应用是求解滑动窗口的最大值或最小值问题。具体的操作步骤如下:

  • 遍历序列,每次将一个元素入队。

  • 如果新入队的元素比队尾元素大(或小),则将队尾元素出队,直到队列满足单调性。

  • 如果队首元素已经不在滑动窗口内,则将队首元素出队。

  • 每次形成一个滑动窗口时,记录队首元素作为最大值或最小值。

例题 #1 滑动窗口 /【模板】单调队列

题目描述

有一个长为 \(n\) 的序列 \(a\),以及一个大小为 \(k\) 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

例如,对于序列 \([1,3,-1,-3,5,3,6,7]\) 以及 \(k = 3\),有如下过程:

\(\begin{array}{|c|c|c|}\hline \textsf{窗口位置} & \textsf{最小值} & \textsf{最大值} \\ \hline \verb![1 3 -1] -3 5 3 6 7 ! & -1 & 3 \\ \hline \verb! 1 [3 -1 -3] 5 3 6 7 ! & -3 & 3 \\ \hline \verb! 1 3 [-1 -3 5] 3 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 [-3 5 3] 6 7 ! & -3 & 5 \\ \hline \verb! 1 3 -1 -3 [5 3 6] 7 ! & 3 & 6 \\ \hline \verb! 1 3 -1 -3 5 [3 6 7]! & 3 & 7 \\ \hline \end{array}\)

输入格式

输入一共有两行,第一行有两个正整数 \(n,k\)。 第二行 \(n\) 个整数,表示序列 \(a\)

输出格式

输出共两行,第一行为每次窗口滑动的最小值 第二行为每次窗口滑动的最大值

【数据范围】 对于 \(50\%\) 的数据,\(1 \le n \le 10^5\); 对于 \(100\%\) 的数据,\(1\le k \le n \le 10^6\)\(a_i \in [-2^{31},2^{31})\)


#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,m,a[N],q[N],h=1,r;

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)//求窗口最小值
    {
        while(h<=r&&a[q[r]]>=a[i])r--;
        q[++r]=i;
        while(h<=r&&q[h]+m<=i)h++;
        if(i>=m)printf("%d ",a[q[h]]);
    }
    printf("\n");
    h=1,r=0;//求窗口最大值
    for(int i=1;i<=n;i++)
    {
        while(h<=r&&a[q[r]]<=a[i])r--;
        q[++r]=i;
        while(h<=r&&q[h]+m<=i)h++;
        if(i>=m)printf("%d ",a[q[h]]);
    }
    return 0;
}

例题 #2 环形最大子序和

题目描述

给定一个环,A[1],A[2],A[3],...A[n],其中A[1]的左边是A[n]. 求一个最大的连续和,长度不超过K。

输入格式

第一行两个整数N和K(1<=N<=100000 , 1<=K<=N) 接下来一行,n个整数[-1000,1000]

输出格式

满足条件的最大连续和以及起始位置和终止位置。 如果有多个结果,输出起始位置最小的,如果还是有多组结果,输出长度最短的。

100%的数据,n≤100000


/*                                                                                
                      Keyblinds Guide
                    ###################
      @Ntsc 2024

      - Ctrl+Alt+G then P : Enter luogu problem details
      - Ctrl+Alt+B : Run all cases in CPH
      - ctrl+D : choose this and dump to the next
      - ctrl+Shift+L : choose all like this
      - ctrl+K then ctrl+W: close all
      - Alt+la/ra : move mouse to pre/nxt pos'

*/
#include <bits/stdc++.h>
#include <queue>
using namespace std;

#define rep(i, l, r) for (int i = l, END##i = r; i <= END##i; ++i)
#define per(i, r, l) for (int i = r, END##i = l; i >= END##i; --i)
#define pb push_back
#define mp make_pair
#define int long long
#define pii pair<int, int>
#define ps second
#define pf first

// #define innt int
// #define inr int
// #define mian main
// #define iont int

#define rd read()
int read(){
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
      xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}

#define ell dbg('\n')
const char el='\n';
const bool enable_dbg = 1;
template <typename T,typename... Args>
void dbg(T s,Args... args) {
    if constexpr (enable_dbg){
    cerr << s;
    if(1)cerr<<' ';
        if constexpr (sizeof...(Args))
            dbg(args...);
    }
}

#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }
#else
#define dbg(...)
#endif


const int N = 3e5 + 5;
const int INF = 1e18;
const int M = 1e7;
const int MOD = 1e9 + 7;


int a[N];
int sum;
list<int> q;
int qz[N];

int n,K;

inline int mod(int x){
    if(x>n)return x-n;
    return x;
}


void solve(){
    n=rd,K=rd;
    for(int i=1;i<=n;i++){
        a[i]=a[i+n]=rd;
    }

    int ans=a[1];
    int ansl=1,ansr=1;

    int l=1;
    int sum=0;
    for(int i=1;i<=2*n;i++){
        qz[i]=qz[i-1]+a[i];
    }

    for(int i=1;i<=2*n;i++){
        while(q.size()&&q.front()<i-K)q.pop_front();
        if(q.size()){
            if(qz[i]-qz[q.front()]>ans){
                ans=qz[i]-qz[q.front()];
                ansl=q.front()+1;
                ansr=i;
            }else if(qz[i]-qz[q.front()]==ans&&i-q.front()<ansr-ansl){
                ans=qz[i]-qz[q.front()];
                ansl=q.front()+1;
                ansr=i;
            }
        }

        while(q.size()&&qz[q.back()]>qz[i])q.pop_back();
        q.push_back(i);


    }
    cout<<ans<<' '<<mod(ansl)<<' '<<mod(ansr)<<endl;

}

signed main() {
    // freopen(".in","r",stdin);
    // freopen(".in","w",stdout);

    int T=1;
    while(T--){
        solve();
    }
    return 0;
}

单调队列优化 dp

单调队列优化