问题背景
想象一下,我们的朋友小R正在进行一场徒步旅行。这次旅程需要 N 天完成。每天,小R都会经过一个补给站,可以购买食物补充能量。不过,补给站的食物价格可不便宜,而且小R的背包只能装 K 份食物,这意味着他需要非常精打细算,才能以最低的花费完成整个旅程。
问题的核心是:
- 小R每天消耗 1 份食物,但由于背包容量有限,他必须决定何时购买多少食物,才能既满足需求,又最小化支出。
问题描述
-
输入:
N: 徒步旅行的总天数。K: 背包最多能装的食物份数。data[i]: 第i天的食物单价。
-
输出:
- 最低的总花费,让小R能够顺利完成旅程。
动态规划解法的直观解释
我们用 动态规划 来解决这个问题,模拟小R每天背包里的状态,计算出到达每一天时的最小花费。
核心思想
假设我们用 dp[i][j] 表示:
- 到第
i天时,背包里剩下j份食物的最小花费。
为了计算 dp[i][j],我们需要考虑:
- 第
i-1天的背包里有多少食物剩余(假设为l)。 - 第
i天需要购买多少食物,才能保证背包在消耗 1 份之后,仍然有j份食物。
食物购买量为:j - l + 1,需要满足以下条件:
- 购买量不能超过背包容量
K。 - 购买量不能为负,即不能“卖出”食物。
状态转移方程
状态转移关系可以表示为:
dp[i][j]=min(dp[i][j],dp[i−1][l]+(j−l+1)×data[i−1])dp[i][j] = \min(dp[i][j], dp[i-1][l] + (j - l + 1) \times data[i-1])dp[i][j]=min(dp[i][j],dp[i−1][l]+(j−l+1)×data[i−1])
其中:
l是第i-1天剩余的食物量。(j - l + 1)是第i天需要购买的食物数量。
动态规划实现(C++)
以下是完整的 C++ 实现代码:
cpp
复制代码
#include <iostream>
#include <vector>
#include <climits> // for INT_MAX
using namespace std;
int solution(int n, int k, vector<int> data) {
// 初始化 dp 数组
vector<vector<int>> dp(n + 1, vector<int>(k + 1, INT_MAX));
dp[0][0] = 0; // 第 0 天无花费
// 动态规划计算最优解
for (int i = 1; i <= n; i++) { // 遍历每一天
for (int j = 0; j <= k; j++) { // 当前背包剩余食物量
for (int l = 0; l <= k; l++) { // 前一天背包剩余食物量
int buy = j - l + 1; // 需要购买的食物数量
if (buy >= 0 && buy <= k) { // 确保合法
if (dp[i - 1][l] != INT_MAX) { // 前一天有解
dp[i][j] = min(dp[i][j], dp[i - 1][l] + buy * data[i - 1]);
}
}
}
}
}
// 返回结果:第 n 天背包空的最小花费
return dp[n][0];
}
int main() {
// 测试样例
cout << (solution(5, 2, {1, 2, 3, 3, 2}) == 9) << endl;
cout << (solution(6, 3, {4, 1, 5, 2, 1, 3}) == 9) << endl;
cout << (solution(4, 1, {3, 2, 4, 1}) == 10) << endl;
return 0;
}
解题过程与优化
如何理解状态转移?
我们以 solution(5, 2, {1, 2, 3, 3, 2}) 为例:
-
第 1 天:
- 小R需要买 1 份食物,花费
1。 - 背包中剩余
1份食物,状态为dp[1][1] = 1。
- 小R需要买 1 份食物,花费
-
第 2 天:
- 小R从背包中取出 1 份食物,背包空了。
- 他又需要买 2 份,背包装满,花费
2 * 2 = 4,状态为dp[2][2] = 5。
-
依此类推,动态规划会逐步计算每一天的最优策略。
时间复杂度分析
-
三重循环:
- 天数循环:
O(n) - 背包剩余状态循环:
O(k) - 状态转移循环:
O(k)
- 天数循环:
-
总时间复杂度:
O(n * k^2)。
思考与优化
1. 空间优化
我们可以使用滚动数组,将二维数组优化为一维数组,减少空间消耗。
2. 加速状态转移
利用单调队列维护最小值,可以将状态转移的复杂度从 O(k) 降低到 O(1),整体时间复杂度降为 O(n * k)。