寒假训练 5(单调队列)

单调队列是一种队首,队尾都可以执行出队操作的单调递增、递减队列,是一种数据结构。主要用来维护一个区间内最大值、最小值的问题,因为有优化复杂度的作用而被使用。
因为单调队列的题目大多都是队首、队尾出队,队尾入队,因此代码较单调栈相比没有那么灵活多变。
(维护最小值)模板,最大值同理:

for(i=1;i<=n;i++)//最小值
   {
       while(r>=l&&q[r]>=a[i])//队尾出队,如果入队元素比队中元素小,那么之前的元素肯定不会再次作为最小值了
        r--;
       q[++r]=a[i];//队尾入队
       p[r]=i;//记录元素位置
       while(p[l]<=i-k)//队首出队,如果队列中的元素处于范围外,那么执行出队
        l++;
   }

那么来几道例题:

洛谷P1886 滑动窗口
在这里插入图片描述
给出数组大小n和窗口长度k,输出每个窗口的最小值和最大值。
单调队列的板子题,写出维护最小值和最大值的单调队列即可

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
int p[1000001],q[1000001],a[1000001];
int main()
{
   ios_base::sync_with_stdio(false);
   cin.tie(NULL);
   int n,k,i,j,l,r;
   cin>>n>>k;
   for(i=1;i<=n;i++)
   {
       cin>>a[i];
   }
   l=1;r=0;//队头,队尾
   for(i=1;i<=n;i++)//最小值
   {
       while(r>=l&&q[r]>=a[i])//队尾出队,如果入队元素比队中元素小,那么之前的元素肯定不会再次作为最小值了。
        r--;
       q[++r]=a[i];//更新队尾元素
       p[r]=i;
       while(p[l]<=i-k)//队首出队,如果队列中的元素处于窗口外,那么执行出队
        l++;
       if(i>=k) cout<<q[l]<<" ";//输出此时单调队列的头元素(因为单调,所以最前面的是最小的)
   }
   cout<<endl;
   l=1;r=0;
   for(i=1;i<=n;i++)//最大值同理
   {
       while(r>=l&&a[i]>=q[r])
        r--;
       q[++r]=a[i];
       p[r]=i;
       while(p[l]<=i-k)
        l++;
       if(i>=k) cout<<q[l]<<" ";
   }
   cout<<endl;
}

洛谷P2032 扫描
有一个 1×n 的矩阵,有 n 个整数。现在给你一个可以盖住连续 k 个数的木板。一开始木板盖住了矩阵的第 1∼k 个数,每次将木板向右移动一个单位,直到右端与第 n 个数重合。每次移动前输出被覆盖住的数字中最大的数是多少。
滑动窗口的青春版,只要写出最大值的那一半就好了。

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
int a[2000001],p[2000001],q[2000001];
int main()
{
   ios_base::sync_with_stdio(false);
   cin.tie(NULL);
   int k,n,i,j;
   cin>>n>>k;
   for(i=1;i<=n;i++)
   {
       cin>>a[i];
   }
   int l=1,r=0;
   for(i=1;i<=n;i++)
   {
       while(r>=l&&a[i]>=q[r]) r--;
       q[++r]=a[i];
       p[r]=i;
       while(i-k>=p[l]) l++;
       if(i>=k)
       {
           cout<<q[l]<<endl;
       }
   }
}

洛谷P1714 切蛋糕
简而言之,给出数组的大小n和序列最长长度m,要求找到不超过m长度的一段序列的和最大。
连续序列和,很显然要用到前缀和的知识。数据为500000,不能暴力求解,需要用到单调队列进行优化。前缀和的公式为sum[i]-sum[j-1],sum[i]无法改变,那么我们就是要用单调队列去优化sum[j-1]的最小值使得公式值最大。

#include <bits/stdc++.h>
#define endl '\n'
#define INF 2147483647
using namespace std;
typedef long long ll;
const int N=500001;
int a[N],sum[N],p[N];
int main()
{
   ios_base::sync_with_stdio(false);
   cin.tie(NULL);
   int k,n,i,j;
   cin>>n>>k;
   for(i=1;i<=n;i++)
   {
       cin>>a[i];
       sum[i]=sum[i-1]+a[i];//前缀和
   }
   int l=1,r=1;//注意优化sum[j-1]时它的值可能为0,即什么也不取,因此r要初始化为1
   int ans=-INF;
   p[1]=0;
   for(i=1;i<=n;i++)
   {
       while(r>=l&&sum[i]<=sum[p[r]]) r--;//队尾出队优化最小值
       p[++r]=i;
       while(r>=l&&p[l]<i-k) l++;//队首出队剃掉超过范围的最小值
       ans=max(ans,sum[i]-sum[p[l]]);//递推出答案
   }
   cout<<ans<<endl;
}

洛谷P2629 好消息,坏消息
切蛋糕的升级版,给出消息数组的大小n。每个元素有一个好坏度,会影响老板的心情。可以选择一个k值,先报告k-n的消息,再报告1-k的消息,期间老板的心情如果下降到0以下,则主人公会被炒鱿鱼。计算有几种方案主人公不会被炒鱿鱼。
k可以取1-n,这样报告消息呈一个环形,所以我们先断环成链让它处在一条线上。然后分析,在每一种报告方案中,我们不能让老板的心情下降到0以下,实际上就是如果报告中最小的前缀和sum[i]-不变的sum[j-1]如果都不小于0,那么这种方案在过程中也不会小于0。那么就变成了我们要去优化sum[i]的最小值就可以了。

#include <bits/stdc++.h>
#define endl '\n'
#define INF 2147483647
using namespace std;
typedef long long ll;
const int N=2000002;
int a[N],sum[N],p[N];
int main()
{
   ios_base::sync_with_stdio(false);
   cin.tie(NULL);
   int n,i,j;
   cin>>n;
   for(i=1;i<=n;i++)
   {
       cin>>a[i];
   }
   for(i=n+1;i<=2*n-1;i++)
   {
       a[i]=a[i-n];//断环成链
   }
   for(i=1;i<=2*n-1;i++)
   {
       sum[i]=sum[i-1]+a[i];//前缀和
   }
   int l=1,r=1;
   int ans=0;//方案数
   for(i=1;i<=2*n-1;i++)//优化sum[i]的最小值
   {
       while(r>=l&&sum[i]<=sum[p[r]]) r--;
       p[++r]=i;
       while(r>=l&&i-n>p[l]) l++;
       if(i>=n)
       {
           if(sum[p[l]]-sum[i-n]>=0) ans++;
       }//如果方案中最小的心情数都不小于0,那么整个报告过程中老板的心情也不会小于0,方案数+1。
   }
   cout<<ans<<endl;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值