单调队列(数列中长度不超过k的子序列和的最值)

时间:2021-03-13 17:39:37

★实验任务

小 F 很爱打怪,今天因为系统 bug,他提前得知了 n 只怪的出现顺序以及击 倒每只怪得到的成就值 ai。设第一只怪出现的时间为第 1 秒,这个游戏每过 1 秒 钟出现一只新怪且没被击倒的旧怪消失。小 F 决定发动一次技能,他的技能最多 维持 k 秒,他希望获得最大的成就值,请你帮他计算他发动技能的时间 l 和技能 结束时间 r(r-l+1<=k)。当存在多种方案使得成就值最大时,选择技能发动时间 l 最小的方案,再选择技能持续时间 r-l+1 最小的方案。

★数据输入

输入第一行为两个正整数 n(1<=n<=100000),k(0<k<=n),表示出现 n 只怪, 小 F 的技能最多维持 k 秒。 输入第二行为 n 个整数,表示小 F 击倒第 i 秒钟出现的怪能给有获得的成就 值 ai(-1000<=a[i]<=1000)。

★数据输出

输出为一行三个数。第一个数为可获得的最大成就值,第二个数为技能发动 时间 l,第三个数为技能结束时间 r。

测试样例

输入:

6 3

-1 2 6 5 -5 6

输出:
6 4 6

单调队列的自我理解(这个例子是从某个大神的博客中看到的):

例题:有一组数据1,5,9,4,7,8,6,他们会依此输入,同时,在某一时刻会让你求出后n个数中的最大值。

根据题意,我们可以得出这样一个结论,若后一个数大于前一个数,则结果必定不会是前一个数(比如现在输入了1,5,由于1<5,所以无论是后几个数中的最大值均不会为1),因此,我们只需维护一个单调递减的数组便可快速求得所需值。(数组变化如下:输入——1,数组——1;输入——5,由于5>1删去1添入5,数组——5;输入——9,由于9>5删去5添入9,数组——9;输入——4,由于4<9直接添入,数组——9,4;输入——7,由于7>4同时7<9因此删去4添入7,数组——9,7;输入——8,由于8>4同时8<9因此删去7添入8,数组——9,8;输入——6,由于6<8直接添入,数组——9,8,6。)总的来说,它的本质就是当你在插入一个值时,应将在他之前存入的所有小于他的数值剔除,再将他存入数组中。

xfdg题解题思路:显然暴力是行不通的,对于此类子序列求和以及存储下标的问题通常是可以用单调队列写的,用一个sum数组储存数列的前缀和,然后通过一个que数组根据题目意思存储数组下标。

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define MAX 100005
#define INF 100000000

using namespace std;

int sum[MAX] = { 0 };
int que[MAX];

int main()
{
int n, k, i, j;
cin >> n >> k;
for (i = 1; i <= n; i++)
{
int tmp;
cin >> tmp;
sum[i] = sum[i - 1] + tmp;
}
int front = 0, tail = 0, ans = -INF;
int ansr, ansl;
for (i = 1; i <= n; i++)
{
while (tail > front&&sum[i - 1] < sum[que[tail - 1]])
tail--;
que[tail++] = i - 1;
while (tail > front&&i - que[front] > k)
front++;
if (ans < sum[i] - sum[que[front]])
{
ans = sum[i] - sum[que[front]];
ansl = que[front] + 1; ansr = i;
}
}
printf("%d %d %d\n",ans, ansl, ansr);
return 0;
}

还会写几道有关单调队列的题目,比如求数列中长度内的最大值最小值问题,然后给出链接,多个例题一起看会稍微更好理解一些,到时候再把链接贴上;