时间限制: 1Sec 内存限制: 128MB
题目描述
一个旅行者有一个最多能装 M 公斤的背包,现在有 n 件物品,它们的重量分别是W1,W2,...,Wn,它们的价值分别为C1,C2,...,Cn,求旅行者能获得最大总价值。
输入
第一行:两个整数,M(背包容量,M≤200)和N(物品数量,N≤30);
第2..N+1行:每行二个整数Wi,Ci,表示每个物品的重量和价值。
输出
仅一行,一个数,表示最大总价值。
样例输入
10 4 2 1 3 3 4 5 7 9
样例输出
12
思路:动态规划,dp[i][j]表示在有i件物品,背包容量为j时的最大价值。状态转移的思路是若放的下当前物品,则看看放入和不放入哪个价值更高,若放不下则与不放当前物品的价值相同,代码如下:
#include <bits/stdc++.h>
using namespace std;
int dp[205][205];//横坐标表示当前背包体积,纵坐标表示当前物品重量,dp值表示当前总价值
int w[35],c[35];
int main() {
int n,m;
cin >> m >> n;
for(int i=1;i<=n;i++)
scanf("%d%d",&w[i],&c[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(j<w[i]) dp[i][j] = dp[i-1][j];
else dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
// for(int i=0;i<=n;i++) {
// for(int j=0;j<=m;j++)
// printf("%d ",dp[i][j]);
// printf("\n");
// }
cout << dp[n][m];
return 0;
}
注释部分可以查看dp数组的打表情况。
在编码过程中,不明白为什么j每次都从0开始,难道不应该从剩余的背包容量开始吗?
后来发现可以这样理解:内层循环可以看作m个背包问题,对于当前有的物品,对背包容量从0到v都做一次决策。
程序可以优化,因为dp具有无后效性,做为原问题最优解的组成部分每个子问题的解也是本身的最优解, 所以
1.多加入一个物品后的决策只与加入这个物品前的结果有关系,所以直接求出最优将其覆盖即可,这是滚动数组的思想。
2.背包容量从最大开始遍历,因为要用到到上一轮的结果并且只会用到小于等于当前背包容量的上一轮结果,为了避免较小的容量更先被覆盖,所以从大容量开始考虑。
代码如下:
#include <bits/stdc++.h>
using namespace std;
int dp[205];
int w[35],c[35];
int main() {
int n,m;
cin >> m >> n;
for(int i=1;i<=n;i++)
scanf("%d%d",&w[i],&c[i]);
for(int i=1;i<=n;i++) {
for(int j=m;j>=0;j--)
if(j>=w[i]) //滚动数组
dp[j] = max(dp[j],dp[j-w[i]]+c[i]);
// for(int k=0;k<=m;k++)
// printf("%d ",dp[k]);
// printf("\n");
}
cout << dp[m];
return 0;
}
注释部分仍然可以查看dp数组的打表情况,可以发现,和第一种是一样的。
题目来源:题目 2131: 信息学奥赛一本通T1267-01背包问题