前置知识:

  

  队列

单调栈

  思考这样一个问题:给定一个数列,询问每一个数左边的第一个比它小的数。

  暴力的做法是:记录下所有读进来的数,然后,每次向前查找,预计时间复杂度O(n2),而且容易被卡。

  仔细思考一下,可以发现,这个做法之所以效率低下,是因为每一次都重复查找了许多肯定不是最优解的元素。很明显,如果当前查找时这个元素不是最优解,那么在之后的查找中它也不会是最优解。我们可以用一个栈来维护:每一次把不是最优解的元素出栈,然后把当前的元素加入栈中。由于每个元素最多进栈一次、出栈一次,因此这个算法的效率是O(n)的。

  上例中,我们使用的这个栈就是一种简单的单调栈。单调栈中的元素具有单调性,而为了保证这一点,在元素加入栈中前需要把栈中所有破坏单调性的元素删除。

  如果从正反两个方向各扫一遍单调栈,可以处理出每个元素在哪一段区间中是最小的。

  放上代码:

int n,top,a[N],b[N];
//a[N]为原序列,b[N]记录维护的答案 
struct node{
    int pos,val;
}q[N];
//单调栈:pos记录在原序列中的位置,val记录权值 
void work(){
    for(int i=1;i<=n;i++){
        while(top&&q[top].val<a[i]) b[q[top].pos]=i,top--;//维护单调性 
        q[++top].val=a[i];q[top].pos=i;
    }
}

 

单调队列

  单调队列与单调栈的原理是一样的。但它还可以通过移动头指针来及时排除那些已在范围之外的答案以保证最终答案的正确性。同单调栈一样,由于每个元素最多入队一次、出队一次,单调队列的效率也是O(n)的。

  放上代码:

int n,k,l,r,d[N],a[N],b[N];
//k为指定的区间长
struct node{
    int pos,val;
}q[N];
void work(){
    l=1,r=0;
    for(int i=1;i<=n;i++){
        while(q[l].pos<i-k+1&&l<=r) l++;//先调整队首指针以排除非法答案
        while(a[i]>=q[r].val&&l<=r) r--;//维护单调性
        q[++r].pos=i;q[r].val=a[i];b[i]=q[l].val;
    }
}