工作还是休息
- 难度 提高
- 时限 1s
- 空限 256m
- 环形 DP
数据规模:,。
思路
工作日产量只由它所在空档的长度决定。 把环上相邻两个休息日之间连续的工作日称为一个「空档」,长度记为 。某工作日若处在某空档中、是从左端休息日数起的第 天(),那么它左边最近的休息日恰是空档左端、右边最近的休息日恰是空档右端,距离分别为 天与 天。环上的「前一个 / 后一个休息日」就是空档的两端,跨周也不会出现别的休息日,所以这对距离没有歧义,产量恒为 。
于是整段空档的贡献只取决于 :
把 写成闭式。 时 依次为 ; 时依次为 。因此
用 的前缀和即可 求出每个 ,把 全算出来只需 。
把环拆成「段」。 让每个休息日连同它顺时针方向的空档组成一段:一段占 天,贡献 ; 表示这个休息日与下一个休息日相邻,段长 、贡献 。环上选定若干休息日,就把 天恰好分成若干段,段长之和为 ,总产量是各段贡献之和。
反过来,任意一组段长 只要满足 ,都能按顺序绕环摆成「休息日 + 个工作日」的形式,总产量正是 。所以原问题等价于:用长度 的段(长度 的段价值 )恰好拼满 天,最大化总价值。每种长度可以重复使用,这是一个完全背包。
状态与转移。 表示恰好用掉 天时能取得的最大总产量:
答案是 。以外层枚举段长、内层让 从小到大递推的完全背包顺序实现即可。
边界。 段长 ()的段永远可以取且贡献为 ,因此每个 都可达, 全部初始化为 就够了,不必引入负无穷。 时只有 可用,答案为 ,正对应「一周只有一天且必须休息」。
检验。 样例 1 中 、、,取一段 得答案 。、 时两天不能都工作(否则没有休息日),只能一休一工,工作日离休息日 天,答案是 而非 ;对 枚举全部 种安排暴力对拍可以确认这一点。
复杂度
时间 , 时约 次转移;空间 。答案不超过 ,需要 64 位整数。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
main() {
let reader = getStdIn()
let n = Int64.parse(reader.readln().getOrThrow())
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let pre = Array<Int64>(n + 1, { _ => 0 })
for (i in 0..n) {
pre[i + 1] = pre[i] + a[i]
}
let g = Array<Int64>(n, { _ => 0 })
for (l in 1..n) {
let m = l / 2
var v = 2 * pre[m]
if (l % 2 == 1) {
v += a[m]
}
g[l] = v
}
let dp = Array<Int64>(n + 1, { _ => 0 })
for (l in 0..n) {
let sz = l + 1
for (s in sz..=n) {
let cand = dp[s - sz] + g[l]
if (cand > dp[s]) {
dp[s] = cand
}
}
}
println(dp[n])
}