• 单调队列(学习笔记)


    建议不了解STL的读者先了解几个基本的队列的STL.这也是单调队列和单调栈一般都会用到的.

    单调队列:建立一个队列,使队列一直具有单调性(满足单调递增或者单调递减),时间复杂度O(N).

    那么我们应该如何做到"使队列一直具有单调性"呢?

    以单调递增为例,我们O(N)扫描整个序列,每扫描到一个元素:

    1 如果该元素大于等于队列末尾元素,则直接入队;

    2 而如果该元素小于已有队列的末尾元素,即不满足单调递增,则使队列中的末尾元素出队,直到该元素符合入队条件,然后入队.

    如果只到这里,那么我们仅仅做到的是最后得到了一个单调队列,是求不出题目所要求的答案的,所以我们需要在第2种情况时,在每次出队前或者出队时,维护一些我们需要的信息(如队列的长度,队列的最值等).

    单调栈其实就是队列变成栈,没有太大差别.

    先来看两道一模一样的例题.

    切蛋糕

    最大子序和:

    输入一个长度为(n)的整数序列,从中找出一段不超过(m)的连续子序列,使得整个序列的和最大.

    分析:

    计算"区间和"问题,很容易想到转化为两个前缀和相减的形式.

    (sum[i])表示序列前i项的和,则区间([l,r])的和即为(sum[r]-sum[l-1]).所以我们现在就是要求出l,r,使得(sum[r]-sum[l])最大,并且(r-l<=m).(你也可以理解为找到一组l,r,使得(sum[r]-sum[l-1])最大,并且(r-l+1<=m))

    我们枚举右端点(i),当i固定时,问题进一步转变为找到一个左端点(j),使得(sum[j])最小(这样才能保证(sum[i]-sum[j])最大),同时注意(j)的位置是在([i-m,i-1])上.

    此时单调队列的核心来了:

    假设有一个位置(k)(j),如果(k<j<i)并且(sum[k]>=sum[j]),那么对于所有大于等于(i)的右端点,(j)一定是比(k)更好的一个左端点.因为(j)(k)在位置上更靠近(i),更不容易超过序列长度(m)的限制,而且(sum[j])(sum[k])还小,更符合序列和最大的性质.

    我们可以戏称为"一个人比你小还比你强,你就废了".

    其实这就是单调队列的思想:及时排除一定不是最优解的解.

    int l=1,r=1;
    //队列左右端点初始化
    q[1]=0;
    //队列第1号元素赋值为零,防止下标越界
    for(int i=1;i<=n;i++){
    	while(l<=r&&q[l]<i-m)l++;
    //如果队头位置与当前右端点i的距离超过m,出队
    	ans=max(ans,sum[i]-sum[q[l]]);
    //此时队头就是右端点为i时,左端点j的最优选择
    //这里我想详细讲一下为什么看似只满足序列长度
    //不超过m的队头一定是最优解
    //根据上面核心算法可知,这个队头被保留了下来
    //一定是因为它的sum比它后面的sum小
    //否则它就是那个又老又弱的元素,早就被淘汰了
    //它既然留了下来,说明它虽然老,但是老当益壮
    	while(l<=r&&sum[q[r]]>=sum[i])r--;
    	q[++r]=i;
    //i作为右端点的情况讨论完了
    //此时它需要被作为将来的左端点而入队
    //而在它入队前我们需要淘汰掉那些
    //比它老还比它弱的东西
    }
    

    滑动窗口:

    现在有一堆数字共(N)个数字((N<=10^6)),以及一个大小为(k)的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

    分析:上题是求区间和最大值,本题是求区间最值,应该也算是很经典的模板题了吧,为了推广一下STL,我用了STL的队列,当然也可以像上题那样数组模拟队列.

    以求区间最大值为例:

    deque<int> q;
    //建立一个双端队列
    for(int i=1;i<=n;i++){
    	while(!q.empty()&&a[i]<a[q.back()])
        	q.pop_back();
    	q.push_back(i);
    //在把当前元素i入队前我们需要淘汰掉那些
    //比它老还比它弱的东西
    	while(q.front()<=i-k)
        	q.pop_front();
    //如果队头位置与当前右端点i的距离超过k,出队
    	ans1[i]=a[q.front()];
    //记录下当前窗口下的最大值
    for(int i=k;i<=n;i++)
    	printf("%d ",ans1[i]);
    //注意一下输出范围
    }
    

    工作调度:

    在任一时刻可以选择(n)项工作中的任意一个工作来做,每项工作花一个单位时间,对于第(i)个工作,截止时间(d[i]),获利(p[i]).

    分析:把n个工作按照截止时间从小到大排序,根据单调队列的思想,建立一个小根堆

    priority_queue<int,vector<int>,greater<int> >q;
    

    对于第i项工作,如果队列(小跟堆)中的元素个数小于它的截止时间(a[i].x),直接入队(小跟堆),同时最大获利ans累加(a[i].y).

    如果队列(小跟堆)中的元素个数大于等于它的截止时间(a[i].x),与队首(堆顶)元素比较获利值,谁大谁留下.
    同时记得更新ans值和队列(小跟堆)

    for(int i=1;i<=n;i++){
    	if(a[i].x<=q.size()){
    	    if(a[i].y>q.top()){
    			ans=ans+a[i].y-q.top();
    			q.pop();
    			q.push(a[i].y);
    	    }
    	}
    	else{
    	    q.push(a[i].y);
    	    ans+=a[i].y;
    	}
    }
    

    一道很容易的省选题:小组队列

    (m)个小组,(n)个元素,每个元素属于且仅属于一个小组。

    支持以下操作:

    push x:使元素x进队,如果前边有x所属小组的元素,x 会排到自己小组最后一个元素的下一个位置,否则x排到整个队列最后的位置。

    pop:出队,弹出队头并输出出队元素,出队的方式和普通队列相同,即排在前边的元素先出队。

    分析:建立两个队列queue,一个维护整个大队列(即只有小组组号的队列),另一个维护对于大队列中的每个元素(即每个小组)内部的元素.

    #include<bits/stdc++.h>
    using namespace std;
    inline int read(){
        int s=0,w=1;
        char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
        return s*w;
    }
    int n,m,Q;
    int a[100005];
    queue <int>b[305];
    queue <int>q;
    int main(){
        n=read();m=read();
        for(int i=0;i<=n-1;i++)a[i]=read();
        Q=read();
        while(Q--){
    	string s;cin>>s;
    	if(s[1]=='u'){
    	    int val;cin>>val;
    	    if(!b[a[val]].empty())b[a[val]].push(val);
    	    else{
    		b[a[val]].push(val);
    		q.push(a[val]);
    	    }
    	}
    	else{
    	    int k=q.front();
    	    printf("%d
    ",b[k].front());
    	    b[k].pop();
    	    if(b[k].empty())q.pop();
    	}
        }
        return 0;
    }
    
    
  • 相关阅读:
    在线自动创建springboot工程
    java线程自带队列的使用以及线程阻塞
    如何分析java内存泄漏问题
    java接口入参模板化,适用于企业化服务远程调度模板化的场景,接口入参实现高度可配置化
    打造springboot高性能服务器(spring reactor的使用)
    docker 5 docker-阿里云加速配置
    docker 4 docker的三要素
    docker 3 docker安装
    docker 2 docker介绍
    docker 1 为什么要使用docker
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10161699.html
Copyright © 2020-2023  润新知