一、题意
输入包含多组测试用例。
每组数据以一个整数n开始,表示每天的食物清单有n种食物。
接下来n行,每行两个整数a和b,其中a表示这种食物可以带给湫湫的幸福值(数值越大,越幸福),b表示湫湫吃这种食物会吸收的卡路里量。最后是一个整数m,表示湫湫一天吸收的卡路里不能超过m。
输出最大幸福值。
二、解析
这是一到换了皮的裸完全背包问题。转化一下题意就是:
有n种物品,每种物品数量无限,每种物品的价值和体积分别为w[i]和v[i]。现在给一个体积为V的背包,要求输出最多能装入的价值量。
这里主要是复习一下完全背包问题的两种解题思路:
方法一:看作DAG问题求解。
这种方法我是根据紫薯的提示学到的,即把原问题看成是一个带边权的DAG最长路问题。每个体积作为一种状态,也就是DAG中的点,指向若干表示装了某一个物品后剩余体积的状态点。边权为装的那个物品的价值。这样原问题就变成了求DAG中的最长路问题了。然后就可以利用记忆化搜索的方法快速解决了。用时O(V*n)方法二:用递推法中的刷表法求解。
这种方法是网上大多数人的做法。即用dp[i]表示剩余体积为i的背包中最多能装的价值量。然后就有转移方程:dp[i] = max(dp[i], dp[i-v[i]]+w[i])。用时也是O(n*V)
注意dp数组只有一维。
三、代码
- 方法一
#include <iostream> // #include <cmath> #define max(x, y) (x)>(y)?(x):(y) using namespace std; const int maxn = 100 + 5, maxv = 100000 + 5; int n, w[maxn], v[maxn], V, memo[maxv]; int dp(int V) { int & res = memo[v]; if(res != -1) return res; res = 0; for(int i = 0; i < n; i ++) if(V >= v[i]) res = max(res, dp(V - v[i]) + w[i]); return res; } int main() { while(cin >> n) { for(int i = 0; i < n; i ++) cin >> w[i] >> v[i]; cin >> V; fill(memo, memo + V + 1, -1); cout << dp(V) << endl; } }
- 方法二
#include <iostream> // #include <cmath> #define max(x, y) (x)>(y)?(x):(y) using namespace std; const int maxn = 100 + 5, maxv = 100000 + 5; int n, w[maxn], v[maxn], V, dp[maxv]; int main() { while(cin >> n) { for(int i = 0; i < n; i ++) cin >> w[i] >> v[i]; cin >> V; fill(dp, dp + V + 1, 0); for(int i = 0; i < n; i ++) { for(int j = v[i]; j <= V; j ++) { dp[j] = max(dp[j], dp[j - v[i]] + w[i]); } } cout << dp[V] << endl; } }
在用递推法时,在本题中i循环和j循环的顺序关系不大,但在某些题目情境下,需要特别注意顺序问题,比如 这道题目( https://blog.nowcoder.net/n/8c089bfcadc9477f8ba3be50088838ed )中,需要求的是种数,则必须i循环在外。并且在这种题目下,也没有办法使用DAG的方法解决了,只能使用刷表法。
四、归纳
- 完全背包问题:即将n种物品放入容量为V的背包中, 每种物品有无限个。
- 可以转化为DAG最长路问题求解,也可以用刷表法解决。
- 转移方程: dp[i] = max(dp[i], dp[i-v[i]]+w[i])
- dp[i]表示剩余体积为i的背包中最多能装的价值量