题解:洛谷 P1353 Running
【题目来源】
洛谷:P1353 [USACO08JAN] Running S - 洛谷(luogu.com.cn)
【题目描述】
奶牛们打算通过锻炼来培养自己的运动细胞,作为其中的一员,贝茜选择的运动方式是每天进行 \(n\) 分钟的晨跑。在每分钟的开始,贝茜会选择下一分钟是用来跑步还是休息。
贝茜的体力限制了她跑步的距离。更具体地,如果贝茜选择在第 \(i\) 分钟内跑步,她可以在这一分钟内跑 \(d_i\) 米,并且她的疲劳度会增加 \(1\)。不过,无论何时贝茜的疲劳度都不能超过 \(m\)。
如果贝茜选择休息,那么她的疲劳度就会每分钟减少 \(1\),但她必须休息到疲劳度恢复到 \(0\) 为止。在疲劳度为 \(0\) 时休息的话,疲劳度不会再变动。晨跑开始时,贝茜的疲劳度为 \(0\) 。
还有,在 \(n\) 分钟的锻炼结束时,贝茜的疲劳度也必须恢复到 \(0\),否则她将没有足够的精力来对付这一整天中剩下的事情。
请你计算一下,贝茜最多能跑多少米。
【输入】
第一行两个正整数 \(n,m\)。
接下来 \(n\) 行,每行一个正整数 \(d_i\)。
【输出】
输出一个整数,表示在满足所有限制条件的情况下,贝茜能跑的最大距离。
【输入样例】
5 2
5
3
4
2
10
【输出样例】
9
【核心思想】
-
问题分析:贝茜进行 \(n\) 分钟晨跑,每分钟可选择跑步或休息。跑步增加疲劳度并获得距离,休息减少疲劳度但必须恢复到0才能再次跑步。初始和结束时疲劳度都必须为0,且不能超过上限 \(m\)。求最大跑步距离。这是一个带状态跳跃的二维DP问题,需要同时追踪时间和疲劳度两个维度。
-
算法选择:
- 二维DP:
f[i][j]表示第 \(i\) 分钟,疲劳度为 \(j\) 时的最大跑步距离 - 状态跳跃:疲劳度从 \(j\) 恢复到0可以直接跳跃 \(j\) 分钟(
i+j) - 三种决策:跑步、休息且疲劳度为0、休息且疲劳度大于0(跳跃)
- 二维DP:
-
关键步骤:
- 初始化:
f[0][0] = 0,其余为负无穷 - 状态转移:遍历每分钟 \(i\) 和每种疲劳度 \(j\):
- 跑步(\(j < m\)):
f[i+1][j+1] = max(f[i+1][j+1], f[i][j] + d[i+1]) - 休息且 \(j=0\):
f[i+1][0] = max(f[i+1][0], f[i][0])(继续休息) - 休息且 \(j>0\):
f[i+j][0] = max(f[i+j][0], f[i][j])(跳跃 \(j\) 分钟恢复)
- 跑步(\(j < m\)):
- 统计答案:
f[n][0](\(n\) 分钟后疲劳度为0的最大距离)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times m)\)
- 空间复杂度:\(O(n \times m)\)
-
带状态跳跃的二维DP核心思想:
- 二维状态设计:同时追踪时间 \(i\) 和疲劳度 \(j\) 两个维度
- 状态跳跃优化:疲劳度从 \(j\) 恢复到0需要 \(j\) 分钟,直接跳到
i+j位置 - 三种决策覆盖所有情况:跑步增加疲劳度、休息保持0、休息跳跃恢复
- 边界约束处理:开始时疲劳度为0、结束时疲劳度为0、不超过上限 \(m\)
- 适用于带资源消耗和恢复机制的最优化问题
【算法标签】
普及+ #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 10005, M = 505; // 定义最大物品数N和最大操作次数M
int n, m; // 物品数量n,操作次数m
int f[N][M]; // 动态规划数组,f[i][j]表示前i个物品使用j次操作的最大值
int a[N]; // 存储每个物品的值
signed main() // 主函数
{
cin >> n >> m; // 输入物品数量和操作次数
for (int i=1; i<=n; i++) // 读取所有物品的值
cin >> a[i]; // 输入第i个物品的值
memset(f, -0x3f, sizeof(f)); // 初始化DP数组为负无穷
f[0][0] = 0; // 初始状态:0个物品,0次操作,值为0
for (int i=0; i<n; i++) // 遍历每个物品
{
for (int j=0; j<=m; j++) // 遍历每种操作次数
{
if (f[i][j]<0) continue; // 如果状态无效则跳过
f[i+1][j+1] = max(f[i+1][j+1], f[i][j] + a[i+1]); // 选择当前物品
if (j==0) // 如果操作次数为0
f[i+1][j] = max(f[i+1][j], f[i][j]); // 不选择当前物品
else // 如果操作次数大于0
{
if (i+j<=n) f[i+j][0] = max(f[i+j][0], f[i][j]); // 跳过多个物品
}
}
}
cout << f[n][0] << endl; // 输出最终答案
return 0; // 程序正常结束
}
【运行结果】
5 2
5
3
4
2
10
9
浙公网安备 33010602011771号