原题截图
题意分析
原题题意分析
小R一共有N天的旅行旅程,每天需要消耗1份食物,每天都会遇到一个补给站,每个补给站每份食物的价格不同,最多只能同时携带K份食物。
题意猜测
由于原题题面描述不够清楚,部分题意需要自己猜测。
- 在旅行开始前,小R能否携带食物?
- 每天可以消耗完食物,再去购买食物(将食物补充到
K个),还是需要购买完所有食物后再消耗食物?也就是说,在每天购买完食物并消耗完当天的食物后,小R最多可以拥有K份还是K-1份食物?
编写代码运行,并和题目的样例数据对照,可以猜到如下题意:
- 在旅行开始前,小R不能携带任何食物。
- 小R每天必须购买完所有食物后再消耗食物,即在每天购买完食物并消耗完当天的食物后,小R最多可以拥有
K-1份食物。
解题思路
本题解题的关键是得出小R在哪几天分别购买多少食物,即小R购买食物的策略,解决策略问题的两种常用的算法就是贪心和动态规划。经过思考,可以发现简单的贪心算法无法得出最优解,于是考虑使用动态规划。
使用动态规划解决本题时,可以定义二维数组int dp[][],dp[i][j]代表小R在前i天结束时(购买完食物且消耗完当天食物后),携带j份食物所需要花费的最小金额。转移状态时,可以使用三层循环,并定义状态转移方程dp[i][j] = min(dp[i][j], dp[i-1][j-(x-1)] + x*a[i-1]);,即第i天结束时携带j份食物的最小金额,等于在第i-1天拥有j-(x-1)份食物的情况下,第i天购买x份食物后,前i天花费的总金额。对于初始状态,由于小R在旅行开始前不能携带任何食物,所以初始状态为dp[0][0] = 0。
细节处理方面,由于状态转移时需要从dp[i-1][j-(x-1)]处继承,而dp[][]数组的第二维只有区间的下标是有意义的,因此继承时x的值需要保证0 <= j-(x-1) <= k-1,以x为未知量解不等式,可得j-k+2 <= x <= j+1,用这个不等式可以确定第三层循环中x的取值范围。
最后,当整个dp[][]数组规划完成后,容易得出,由于题意没有对最后一天结束后食物的数量做出特殊要求,并且整个旅途中的任何一份食物都需要花费金钱,所以最后一天结束时,小R不剩余任何食物的方案就是最优的,所以取dp[n][0]为答案(n为旅行天数)。
代码实现
#include <bits/stdc++.h>
using namespace std;
int solution(int n, int k,const vector<int>& a)
{
constexpr int inf=0x3f'ff'ff'ff;
//dp[i][j]:在第i天买完食物并消耗完1份食物后,携带j份食物所花费的最小金额
vector<vector<int>> dp(n+1,vector<int>(k,inf));
dp[0][0] = 0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<k;j++)
{
// 如果可以每天先消耗食物再购买食物
// (或者先购买食物,然后消耗食物,最后再购买食物),则:
// 0 <= j-(x-1) <= k
// 0 <= j-x+1 <= k
// -j-1 <= -x <= -j+k-1
// j-k+1 <= x <= j+1
// 经检验,这个题意不正确。
// --------------------
// 如果必须在每天必须在购买完食物后再消耗食物
// (即消耗食物后无法再购买食物),
// 则每天结束时实际最多可携带的食物为k-1个,所以:
// 0 <= j-(x-1) <= k-1
// 0 <= j-x+1 <= k-1
// -j-1 <= -x <= -j+k-2
// j-k+2 <= x <= j+1
// 经检验此题意正确。
for(int x=max(0,j-k+2);x <= j+1;x++)
{
dp[i][j] = min(dp[i][j],dp[i-1][j-(x-1)]+x*a[i-1]);
}
}
}
return dp[n][0];
}
总结
本题使用动态规划算法解决,时间复杂度为,空间复杂度为。如果你有复杂度更优的解法,欢迎在下方评论。