2 h0173. 01背包问题(输入方式改一下就是一样的题)
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。0<N,V≤1000
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。0<vi,wi≤1000
输出格式:
输出一个整数,表示最大价值。
输入样例:
4 7
1 2
2 4
3 4
4 5
输出样例:
11
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
-----------------------------------------------------------------------------------------------------------------------
其实本题与之前的数字三角形大同小异。
方法一:递归
最简单的递归方式,缺点是做了很多多余和重复的计算
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int t[1010]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
int T; //可以使用的时间
int M; //药材个数
//递归方法解决问题
//需要一个时间数组、一个价值数组
//从序号五开始判断
//选择序号的药材,就减去所需时间,并加上药材价值
//不选择就判断下一序号药品
//当前最大时间超过要求时间就不再计算
//找最大价值的路径
int KnapSack(int i, int j);
int main()
{
//输入总时间T和物品总数M
cin >> T >> M;
for(int i = 1; i <= M; i++)
{
//输入每个药材的时间和价值
cin >> t[i] >> v[i];
}
//调用函数解决问题,为了后期优化为滚动数组,故从后往前
cout << KnapSack(M, T) << endl;
return 0;
}
//i为药材序号、j为剩下的时间
int KnapSack(int i, int j)
{
//当序号遍历完说明结束
//或者所剩时间<=0了,说明来不及了,结束
if(i == 0 || j <= 0)
return 0;
//如果挖完当前药材的时间,比剩下的时间多
//,说明不能挖这个,看挖下一个行不行,序号变为i-1
//由于没挖这个药材,所以所剩时间j没有变
if(t[i] > j)
return KnapSack(i-1, j);
//当挖完当前药材的时间,小于剩下的时间,说明可以挖也可以不挖
//如果挖,挖完之后看下一个行不行,序号变为i-1
//由于挖了这个药材,所以剩余时间j-挖这个药材需要的时间t[i]
//所挖药材的价值 = 当前药材的价值 +
int x = v[i] + KnapSack(i-1, j-t[i]);
//如果不挖,剩余时间j不变
//所挖药材的价值 = 之后判断出来价值最大路径的总价值
int y = KnapSack(i-1, j);
//最后判断两个路径的值哪个大返回哪个
return max(x, y);
}
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/
方法二:递归+备忘录
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int t[110]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
int dp[110][1010];//代表取前i种药品,在时间为j时的最大价值***
int T; //可以使用的时间
int M; //药材个数
//递归方法解决问题
//需要一个时间数组、一个价值数组
//从序号五开始判断
//选择序号的药材,就减去所需时间,并加上药材价值
//不选择就判断下一序号药品
//当前最大时间超过要求时间就不再计算
//找最大价值的路径
int KnapSack(int i, int j);
int main()
{
//输入总时间T和物品总数M
cin >> T >> M;
for(int i = 1; i <= M; i++)
{
//输入每个药材的时间和价值
cin >> t[i] >> v[i];
}
//备忘录初始化为-1***
for(int i = 1; i <= M; i++)
{
for(int j = 1; j <= T; j++)
{
dp[i][j] = -1;
}
}
//调用函数解决问题
cout << KnapSack(M, T) << endl;
return 0;
}
//i为药材序号、j为剩下的时间
int KnapSack(int i, int j)
{
//当序号遍历完说明结束
//或者所剩时间<=0了,说明来不及了,结束
if(i == 0 || j <= 0)
{
return 0;
}
//如果挖完当前药材的时间,比剩下的时间多
//,说明不能挖这个,看挖下一个行不行,序号变为i-1
//由于没挖这个药材,所以所剩时间j没有变***
if(dp[i][j] != -1)
return dp[i][j];
if(t[i] > j)
{
dp[i][j] = KnapSack(i-1, j);
return dp[i][j];
}
//当挖完当前药材的时间,小于剩下的时间,说明可以挖也可以不挖
//如果挖,挖完之后看下一个行不行,序号变为i-1
//由于挖了这个药材,所以剩余时间j-挖这个药材需要的时间t[i]
//所挖药材的价值 = 当前药材的价值 +
int x = v[i] + KnapSack(i-1, j-t[i]);
//如果不挖,剩余时间j不变
//所挖药材的价值 = 之后判断出来价值最大路径的总价值
int y = KnapSack(i-1, j);
//最后判断两个路径的值哪个大返回哪个***
dp[i][j] = max(x, y);
return dp[i][j];
}
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/
方法三:状态转移+备忘录
当 当前时间和药品数量都为0时,得到的价值必为0,在开头我们将dp备忘录初始化为0。然后根据状态转移方程,不选的情况就是直接等于上一个的价值:dp[ i ] [ j ] = dp[ i-1 ][ j ],选的情况就是判断左右分支的大小,选大的那个:max(dp[ i-1 ][ j ] ,dp[ i-1 ] [ j-t [ i ] ]+v[ i ]);
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int t[110]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
int dp[110][1010] = {0};//代表取前i种药品,在时间为j时的最大价值***
int T; //可以使用的时间
int M; //药材个数
int KnapSack(int i, int j);
int main()
{
//输入总时间T和物品总数M
cin >> T >> M;
for(int i = 1; i <= M; i++)
{
//输入每个药材的时间和价值
cin >> t[i] >> v[i];
}
for(int i = 1; i <= M; i++)
{
for(int j = T; j >= 0; j--) //从后往前是为了滚动数组做准备
{
if(t[i] > j)
dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j],dp[i-1][j-t[i]]+v[i]);
}
}
cout << dp[M][T];
return 0;
}
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/
方法四:状态转移+滚动数组
减少了备忘录所需的空间,思考方式与之前做的数字三角形一样。
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int t[110]; //挖某种草药所需的时间
int v[110]; //第i种物品的价值
int dp[1010] = {0};//代表取前i种药品,在时间为j时的最大价值***
int T; //可以使用的时间
int M; //药材个数
int KnapSack(int i, int j);
int main()
{
//输入总时间T和物品总数M
cin >> T >> M;
for(int i = 1; i <= M; i++)
{
//输入每个药材的时间和价值
cin >> t[i] >> v[i];
}
for(int i = 1; i <= M; i++)
{
for(int j = T; j >= 0; j--)
{
if(t[i] > j)
dp[j] = dp[j];
else
dp[j] = max(dp[j],dp[j-t[i]]+v[i]);
}
}
cout << dp[T];
return 0;
}
/*测试用例
70 3
71 100
69 1
1 2
答案:3
13 5
4 9
5 10
4 9
3 2
10 24
答案:28
*/