20 ACwing 288 休息时间 题解
休息时间
题面
在某个星球上,一天由 N 个小时构成,我们称 0 点到 1 点为第 1 个小时、1 点到 2 点为第 2 个小时,以此类推。
在第 i 个小时睡觉能够恢复 \(U_i\) 点体力。
在这个星球上住着一头牛,它每天要休息 B 个小时。
它休息的这 B 个小时不一定连续,可以分成若干段,但是在每段的第一个小时不能恢复体力,从下一个小时开始才能睡着,从而恢复体力。
为了身体健康,这头牛希望遵循生物钟,每天采用相同的睡觉计划。
另外,因为时间是连续的,即每一天的第 N 个小时和下一天的第 1 个小时是相连的(N 点等于 0 点),这头牛只需要在每 N 个小时内休息够 B 个小时就可以了。
请你帮忙给这头牛安排一个睡觉计划,使它每天恢复的体力最多。
\(3 \le N \le 3830\)
\(2 \le B < N\)
\(0 \le U_i \le 2 \times 10^5\)
题解
如果先不考虑两天连着的这个条件,只考虑这一天里该怎么睡
我们需要知道一共睡了多长时间,以及前一小时睡没睡
所以设 \(f(i,j,0/1)\) 表示到第 \(i\) 小时,已经睡了 \(j\) 小时,第 \(i\) 个小时睡/没睡,能获得的最大体力
因为我们不考虑前一天的小时,所以这天的第一个小时一定没有贡献,初始状态 \(f(1, 0, 0) = 0, \ f(1, 1, 1) = 0\) ,目标状态 \(max\{f(N,B,0), \ f(N,B,1)\}\) 转移:
\[\begin{align}
& f(i, j, 0) = max \{ f(i - 1, j, 0), \ f(i - 1, j, 1) \} \\
& f(i, j, 1) = max \{ f(i - 1, j - 1, 0), \ f(i - 1, j - 1, 1) + U_i \}
\end{align}
\]
这样dp就只少了一种第一个小时有贡献的情况,所以我们可以再做一个dp ,强制前一天的第 \(N\) 个小时以及这一天的第一个小时选,初始 \(f(1, 1, 1) = U_i\) ,转移同上,最终答案在两种情况中取个最大值即可
时间复杂度为 \(O(NB)\)
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 3835;
int n, m;
int f[N][2], val[N];
int main () {
cin >> n >> m;
for (int i = 1; i <= n; i ++) {
cin >> val[i];
}
//只考虑当天的情况
memset (f, -0x3f, sizeof f);
f[0][0] = f[1][1] = 0;
for (int i = 2; i <= n; i ++) {
for (int j = min (i, m); j >= 0; j --) {
f[j][0] = max (f[j][0], f[j][1]);
if (j) {
f[j][1] = max (f[j - 1][0], f[j - 1][1] + val[i]);
}
}
}
int ans = max (f[m][0], f[m][1]);
//强制跨天的情况
memset (f, -0x3f, sizeof f);
f[1][1] = val[1];
for (int i = 2; i <= n; i ++) {
for (int j = min (i, m); j >= 0; j --) {
f[j][0] = max (f[j][0], f[j][1]);
if (j) {
f[j][1] = max (f[j - 1][0], f[j - 1][1] + val[i]);
}
}
}
//因为强制跨天,所以最后一天一定会选,所以 f[m][0] 是不合法状态
ans = max (ans, f[m][1]);
cout << ans << endl;
return 0;
}