【题解】月儿的背包

· · 个人记录

题面:

月儿有一些背包

背包可以装背包

背包可以装装了背包的背包

背包可以装装了很多背包的背包...

具体来说,地上有n个位置

你可以看到每个位置上有一个背包,背包从1到n依次编号

一开始第i个背包里有ai个背包

现在月儿要整理一下背包

从第二个背包开始(第一个背包不用整理)

整理第i个背包的方法:

将该背包的前k个背包中所装背包最少的背包j中的背包取出(如果背包j中没有背包,则忽略)

(如果前方不足k个背包,则从第1个背包开始)

放入第i个背包

然后为了保持那个被取出了所有背包的背包j的整理完了的状态

月儿会从她的口袋里(她的口袋也是一个背包)掏出一些背包放入背包j中

使得背包j中的背包数量恢复

整理后,她想知道第n个位置有多少个背包

月儿想了101ms,觉得并不难 于是她不给你样例,并且将时限降至100ms

分析:

(单调队列模板题)

和天明的背包不同,月儿的背包整理是从前k个背包里,挑最少的背包放入(那就不能像天明那么无脑了)

那么我们该如何快速找出前k个背包中所装背包最少的背包呢

先看一个例子

k=3

整理背包时有如下情况

3 4 5 6 7(背包编号)

4 7 6 1 2(所装背包)

我们即将整理第6个背包

粗体为候选背包,设候选背包组成一个队列(会前后进出调整)

这时队列里有3个元素,分别对应背包编号3 4 5

当处理下一个背包时,队列里是4 5 6

编号3的背包出队了,很明显在队列里,左边的元素“存活时间”比右边的元素短

再回到队列3 4 5,对应背包数4 7 6

5背包存活时间比4背包长,而且5背包比4背包更“优秀”(在这里更小即是更优秀),那么在之后的整理中,4背包都不可能发挥作用直到出队,因为4背包在5背包就在,5背包又更优...(如果一个OIer比你小还比你强,你就可以退役了)

为了简化队列,在元素进入队列时就把队列末尾能淘汰的淘汰掉,直到最后一个元素比本元素优秀或队列为空,再把本元素入队,这样队列就一定是单调的,队头即是最优的(这就是单调队列),这样就能快速找出前k个背包中的目标

然后还要考虑元素出队,即将“存活时间”结束了的元素出队,操作很简单

在队列里,左边的元素“存活时间”比右边的元素短,所以从队头开始,如果队头元素“存活结束”,则出队,直到队头元素可以“存活”或队列为空

队列的元素可用结构体,两成员变量,数量v,“存活时间”l

本题结合单调队列依次整理背包即可

总结一下:单调队列的操作:添加(从队尾淘汰一些元素后添加一个元素,维持单调性),清除(从队头清除不该存在于队列中的元素),还要记得判断队列是否为空(不判断会爆)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

struct node{
    long long v,l;
} team[40005];//单调队列
long long n,k,a[40005],h,t;
void tin(long long v,long long l) {//添加元素的操作
    while(h<=t) {//当队列不为空
        if(v>team[t].v) break ;//队尾更优就跳出
        t--;//队尾出队
    }
    ++t;
    team[t].v=v;
    team[t].l=l;//添加
    return ;
}
void tout(long long l) {
    while(h<=t) {//当队列不为空
        if(l<=team[h].l) break;//队头能存活跳出
        h++;
    }
    return ;
}
int main() {
    scanf("%lld %lld",&n,&k);
    for(int i=1;i<=n;i++) {
        scanf("%lld",&a[i]);
    }
    h=t=1;//初始化
    team[1].v=a[1];
    team[1].l=1;
    for(int i=2;i<=n;i++) {
        tout(i-k);//清除,负数也没关系
        a[i]+=team[h].v;//放
        tin(a[i],i);//添加
    }
    printf("%lld",a[n]+1);
    return 0;
}