01背包
按DP题目思考方向思考
DP数组的意义
题中有两个相关联的变量,建立一个dp[i][j]表示选择有i个物品可选择、背包容量为j时能背走的最大价值。
明确递推公式
在选择到第i个物品时,在当前背包容量j大于第i个物品的大小时,我们可以选择拿走第i个物品,也可以选择不拿走这第i个物品,而我们要选择价值的最大值,那么递推公式就有:
dp[i][j]=max(dp [ i - 1 ] [ j ], dp [ i - 1 ] [ j - volume [ i ] ] );
初始化
由dp数组的意义可知,当i=0或者j=0时,dp[i][j]=0;将第一行第一列初始化为0即可。
代码如下
#include <algorithm>
#include <iostream>
using namespace std;
int dp[1001][1001];
int volume[1001];
int value[1001];
int main()
{
int N, V;
cin >> N >> V;
for (int i = 1; i <= N;i++)
cin >> volume[i] >> value[i];
for(int i=1;i<=N;i++)//遍历物品
for (int j = 1; j <= V; j++)//遍历背包
{
if (volume[i] > j)//若背包容量小于该物品大小,这个物品放不下,价值与先前相等
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j - volume[i]] + value[i], dp[i - 1][j]);
}
cout << dp[N][V]<<endl;
return 0;
}
优化
由上面的分析可以看到,二维数组中对当前行的处理都是基于上一行的数据进行的,由此我们可以将一维数组重复利用,因为一维数组的数值都是上一行的映射,直接处理一维数组即可。
但是需要注意:一维数组因为数据不像二维数组那样行行之间互不影响,在遍历过程中如果从前向后遍历因为后面的数据依赖于前一行的前列原始数据,而从前向后遍历会直接改变原始数据,导致物品被反复放入背包,所以应该从后向前遍历。除此之外,物品和背包容量的遍历顺序也不能像二维数组中一样随意颠倒。
代码如下
#include <algorithm>
#include <iostream>
using namespace std;
int dp[1001],volume[1001],value[1001];
int main()
{
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> volume[i] >> value[i];
for(int i=1;i<=N;i++)
for (int j = V; j >= volume[i]; j--)
dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);//这里因为在for循环中已经添加了限制条件所以不用再次判断
cout << dp[V] << endl;
return 0;
}
多重背包
对比上面的01背包,它只是多了一个数限制而已,不用过多处理,多定义一个数组存储数量信息,再加一个判断循环即可。
#include <algorithm>
#include <iostream>
using namespace std;
int dp[1000];
int w[1000];//体积
int v[1000];//价值
int s[1000];//数量
int main()
{
int N, V;
cin >> N >> V;
for (int i = 1; i <= N; i++)
cin >> w[i] >> v[i] >> s[i];
for (int i = 1; i <= N; i++)
for (int j = V; j >= w[i]; j--)
for (int k = 1; k <= s[i] && j >= k * w[i]; k++)
dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
cout << dp[V] << endl;
return 0;
}