单调队列模版(单调队列的应用)

题目描述:

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

注意: 子序列的长度至少是 11。

输入格式

第一行输入两个整数 n,m𝑛,𝑚。

第二行输入 n𝑛 个数,代表长度为 n𝑛 的整数序列。

同一行数之间用空格隔开。

输出格式

输出一个整数,代表该序列的最大子序和。

数据范围

1≤n,m≤3000001≤𝑛,𝑚≤300000,
保证所有输入和最终结果都在 int 范围内。

输入样例:
6 4
1 -3 5 1 -2 3

输出样例:
7

我一看到这个题,首先想到的就是,用前缀和来做,因为题目说了,长度不超过m的``连续子序列 但是长度不超过m这个,我还真不知道该怎么写。最后,我一看这道题的标签是单调队列。于是我知道该怎么做了。如果我们就单纯的使用前缀和没有办法很好的找到最大值,因为我们的时间复杂度太高了 O(n^2),怎么优化呢?问题也就转变成了,求 m + 1区间中的最小值,但是,时间复杂度要求是O(n)因此,使用单调队列就解决了这个问题,废话不多说,直接上代码

#include <iostream>
using namespace std;
const int N = 3e5 + 10;
int q[N], hh, tt;
int s[N], n, m;
void print(){
   for(int i = hh; i <= tt; i ++){
       cout << s[q[i]] << " ";
   }
   cout << endl;
}
int main(){
   cin >> n >> m;
   for(int i = 1; i <= n; i ++){
       cin >> s[i];
       s[i] += s[i - 1];
       //cout << s[i] << " ";
   }
   //cout << endl;
   int res = 0x80000000;
   //找出区间中最小的值
   for(int i = 1; i <= n; i ++){
       if(i - q[hh] > m) hh ++;
       //为什么要把 res 的计算放到最上面呢?
       //因为,如果我们把这条语句放到下面的话,我们就没有判断第一个元素,(就单纯的第一个元素也是一个子序列)
       //如果我们先插入的第一个元素,那么s[i] 和 s[q[hh] 就是同一个值,这样就会导致 res = 0 就永远不会出现负数
       res = max(res, s[i] - s[q[hh]]);
       //队列不为空 && 队尾元素大于插入的元素,那么队尾元素出队(单调队列)
       while(hh <= tt && s[q[tt]] >= s[i]) tt --;
       q[++ tt] = i; // 入队列
       //cout << "当前元素为 " << s[i] << endl;
       //print();
       
   }
   cout << res << endl;
}

为什么初始化的时候,tt 没有等于 -1?

这是因为,我们默认情况下,是在队列中已经插入了一个元素的,因为我们求的是前缀和,插入了一个前缀和标杆0,不加0的话,如果第一个元素能取到的情况下,每次都取不到,模拟下就能明白,因为减的是s[l - 1]
因为求的是i之前,不包括i的长度为m的滑动窗口的最小值,在前m个元素为正值时,s[i]递增,这时窗口内最小值为s[0],表示取1~i个元素求和,所以s[0]应当为第一个元素。这也是为什么先判断队头是否出队,接着取max再将i元素入队的原因(正常是,先将i入队,再判断队头是否出队)。

为什么要先计算res在插入当前元素?

因为把s[i]加到队列里后可能会把之前的最小值弹出,自己变成最小值了,会丢掉之前m长度的最小值记录

都写到这里了,再把单调队列的模版再整理一下叭!!
ACWing 154.滑动窗口
这是单调队列的模版题!!

#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N], n, m;
int q[N], hh, tt = -1;
int main(){
   cin >> n >> m;
   for(int i = 0; i < n; i ++){
       cin >> a[i];
       if(i - q[hh] + 1 > m) hh ++;
       while(hh <= tt && a[q[tt]] >= a[i]) tt --;
       q[++ tt] = i;
       if(i < m - 1) continue;
       cout << a[q[hh]] << " ";
   }
   cout << endl;
   hh = 0, tt = -1;
   for(int i = 0; i < n; i ++){
       if(i - q[hh] + 1 > m) hh ++;
       while(hh <= tt && a[q[tt]] <= a[i]) tt --;
       q[++ tt] = i;
       if(i < m - 1) continue;
       cout << a[q[hh]] << " ";
   }
   cout << endl;
}

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值