1 条题解
-
0
题目详解
问题描述
Vova 初始时刻为 ,一天有 小时。他总共要睡 次,第 次他可以选择在醒来后经过 小时或者 小时开始睡觉(每次睡觉恰好持续 小时,因此我们只关心开始时刻模 的余数)。如果开始时刻模 落在区间 内,则这次睡眠是“好的”。求最多能得到多少次好的睡眠。
问题分析
设第 次睡眠前的时间为 (),第 次的选择为 ,其中 表示加 , 表示加 。那么第 次睡眠的开始时刻为:
我们只关心 是否属于 。
令前缀和 ,则:
因此 ,其中 表示前 次中选择“减 ”的总次数。
动态规划解法
我们可以按照次数依次决策,用 DP 记录当前已使用的“减 ”次数以及获得的好睡眠次数。
状态定义
设 表示考虑了前 次睡眠,恰好使用了 次“减 ”时,能获得的最大好睡眠次数。
其中 ,(因为最多 次减 )。初始状态
,表示还没开始睡觉,没有减 次数,好睡眠数为 。其余状态初始化为 (表示不可达)。
状态转移
对于第 次(),设前缀和 。
已知前 次的状态 ,现在考虑第 次的选择:-
不减 (即 ):那么新的减 总次数仍为 ,新的开始时刻为 。若该时刻落在 内,则这次睡眠好,贡献 ,否则贡献 。
$$dp[i][j] = \max(dp[i][j],\; dp[i-1][j] + \big[ (sum - j) \bmod h \in [l, r] \big]) $$
转移方程: -
减 (即 ):此时减 总次数变为 ,开始时刻为 。
$$dp[i][j+1] = \max(dp[i][j+1],\; dp[i-1][j] + \big[ (sum - j - 1) \bmod h \in [l, r] \big]) $$
转移方程:
答案
最终答案为:
复杂度
状态数 ,转移 ,总复杂度 ,在 时可行。
代码实现(标程风格)
#include <bits/stdc++.h> using namespace std; bool in(int x, int l, int r) { return l <= x && x <= r; } int main() { int n, h, l, r; cin >> n >> h >> l >> r; vector<int> a(n); for (auto &it : a) cin >> it; // dp[i][j]:前i次,用了j次减1,最大好睡眠数 vector<vector<int>> dp(n + 1, vector<int>(n + 1, INT_MIN)); dp[0][0] = 0; int sum = 0; for (int i = 0; i < n; ++i) { sum += a[i]; for (int j = 0; j <= i; ++j) { if (dp[i][j] == INT_MIN) continue; // 选择不加1(即不减1) dp[i + 1][j] = max(dp[i + 1][j], dp[i][j] + in((sum - j) % h, l, r)); // 选择减1 dp[i + 1][j + 1] = max(dp[i + 1][j + 1], dp[i][j] + in((sum - j - 1) % h, l, r)); } } cout << *max_element(dp[n].begin(), dp[n].end()) << endl; return 0; }示例验证
输入:
7 24 21 23 16 17 14 20 20 11 22运行过程(部分):
- 初始 ,。
- 第 次:。
从 :- 不减1:时刻 ,不在 ,。
- 减1:时刻 ,不在区间,。
- …最终 的最大值为 ,输出 ,与题目一致。
总结
本题的关键是将选择“减 ”的次数作为 DP 的第二维,利用前缀和与减 次数的线性关系得到当前时刻,从而判断是否产生好睡眠。该 DP 设计巧妙,时间复杂度 足以通过本题。
-
- 1
信息
- ID
- 6484
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 7
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者