自助餐
- 难度 提高
- 时限 1s
- 空限 256m
- 背包 DP
数据规模:,,。
思路
把「顺序」压缩成一个约束。 题意是:第 道料理在吃完前 道之后才点,也就是在时刻 点餐,而第 道()必须在 之前点出。所有 都是整数,所以点餐时刻也是整数,「」与「」等价。
于是选出一个子集 、把某种顺序 排好,合法当且仅当
最后一道料理的耗时 完全不参与这个限制——它可以在停止点餐后继续吃完。
最后一道放最耗时的那道。 限制式等价于 ,也就是给子集里的一道料理「免单」一个 。要想让式子最容易被满足,免单的应当是 最大的那道;而把它排在最后,其余料理的顺序可以任取(前缀和不超过总和,恒满足不超过 )。所以
带标记的 0/1 背包。 问题变成:从 道料理里挑一个子集,其中至多一道不计入容量(这道就是最后吃的),在容量 下最大化 。用两个数组区分「免单机会是否已经用掉」:
- :从已处理的料理中选了若干道、没有用掉免单机会、耗时和恰为 的最大美味度;
- :同样但已经用掉免单机会、其余料理(不含被免单的那道)耗时和恰为 的最大美味度。
处理第 道料理时( 倒序枚举保证每道只用一次):
注意 与 都直接沿用旧值:因为 倒序枚举, 处的值此时仍是本轮处理 之前的值。同一个 上先算 再算 ,这样 用到的是旧的 。
答案与边界。 答案取 , 超过 的状态直接截断不存。 时只有 一格,退化为「只能吃一道」,,正确; 的料理只能走免单那一支,也自动被 收下。初始 ,其余位置与整个 置为极小值,表示不可达,避免「不可达状态 」被误当成合法解。
复杂度
时间 ,空间 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
main() {
let reader = getStdIn()
let first = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = first[0]
let t = first[1]
let lim = t - 1
let neg: Int64 = -1000000000000000
let nn = n
let limv = lim
var dp0 = Array<Int64>(limv + 1, { _ => neg })
var dp1 = Array<Int64>(limv + 1, { _ => neg })
dp0[0] = 0
for (_ in 0..nn) {
let ab = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let a = ab[0]
let b = ab[1]
var j = limv
while (j >= 0) {
var best = dp1[j]
let v0 = dp0[j] + b
if (v0 > best) {
best = v0
}
if (j >= a) {
let v1 = dp0[j - a] + b
if (v1 > best) {
best = v1
}
let v2 = dp1[j - a] + b
if (v2 > best) {
best = v2
}
}
dp1[j] = best
if (j >= a) {
let w = dp0[j - a] + b
if (w > dp0[j]) {
dp0[j] = w
}
}
j -= 1
}
}
var ans: Int64 = 0
for (j in 0..=limv) {
if (dp1[j] > ans) {
ans = dp1[j]
}
}
println(ans)
}