模板题留名/x

这题就是一个单调队列模板题(真模板题,被各个大佬用于单调队列的讲解)

由于不想水题解,就在这里讲讲单调队列吧。。。

我们用此题来讲解。

假设我们现在在求窗口中的最小值,那么,如果满足存在两个位置:i,j在同一个窗口(i<j),如果a[i]>=a[j],那么,一定有答案不可能是a[i]。(当然,如果a[i]==a[j],我们把答案看做a[j]而不是a[i]也是成立的)

为啥呢?

因为,我们发现,我们滑动“窗口”时,是逐个删除前面的数,也就是说,i一定会比j先被删除。

所以,i还在窗口时,j也肯定在窗口,而,又由于a[i]>=a[j],所以,答案最小值不会取到a[i]的(要取也是取的a[j])

所以,如果窗口存在一个i满足,存在一个j>i,a[i]>=a[j],那么,我们把这个i删除即可

那么,我们看看,如果我们把剩下的数按照下标由小到大排序的话,会是什么样子:

因为不存在i<j,a[i]>=a[j]的情况,所以,剩下的数一定满足:

i<j,a[i]<a[j],也就是说,我们当前窗口的答案就是剩下的点中,下标最小的点的值

那么,我们如果将窗口"滑动"会怎么样?

窗口滑动后,我们会删除一个数,同时添加一个数,我们先考虑删除一个数。

我们有如下讨论:

如果,删除的那个数在我们删除无用的数后的剩下的窗口中,那么,它一定是第一个数。

所以,我们判断下第一个是不是需要删除的那个数,然后判断是否把它删除即可。

然后,我们再来看看添加一个数,因为,我们添加的这个数一定比窗口里面的所有数的下标都大,所以,它一定会留在窗口里面,我们只需用这个数去判断下窗口剩下的数合不合法即可。

设窗口中剩下两个点i,j,这个点为k(i<j<k)

如果a[i]>=a[k],因为a[j]>=a[i],所以,如果i不合法,j肯定也不合法,所以我们可以从"窗口"的后面倒着判断每个点是否合法,然后删除即可。

因为我们注意到,我们的"窗口"需要删除第一个数和最后一个数,所以我们使用双端队列维护即可。

复杂度分析:由于判断O(1),每个点最多进入,删除,队列一次,而且,如果越过这个点遍历其他点的话,这个点一定会被删除,所以,复杂度是O(n)

Ps:由于本题有点特殊,判断第一个数是否删除的时候可以直接写if,不过大多数情况是直接写的while(因为可能与位置无关导致一开始窗口有多个"过期"的点)

本题代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+1;
int a[N],que[N],l=1,r;
int main(){
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
    }
    que[++r]=1;
    for(int i=2;i<=k;++i){
        while(l<=r&&a[que[r]]>=a[i]){
            --r;
        }
        que[++r]=i;
    }
    printf("%d ",a[que[l]]);
    for(int i=k+1;i<=n;++i){
        if(que[l]==i-k){
            ++l;
        }
        while(l<=r&&a[que[r]]>=a[i]){
            --r;
        }
        que[++r]=i;
        printf("%d ",a[que[l]]);
    }
    puts("");
    l=1,r=0;
    que[++r]=1;
    for(int i=2;i<=k;++i){
        while(l<=r&&a[que[r]]<=a[i]){
            --r;
        }
        que[++r]=i;
    }
    printf("%d ",a[que[l]]);
    for(int i=k+1;i<=n;++i){
        if(que[l]==i-k){
            ++l;
        }
        while(l<=r&&a[que[r]]<=a[i]){
            --r;
        }
        que[++r]=i;
        printf("%d ",a[que[l]]);
    }
    return 0;
}