01背包问题
#include<iostream>
#include<stdlib.h>
#include<vector>
#include<cmath>
int main()
{
int M=0; //材料数
int N=0; //背包容量
std::cin>>M>>N;
std::vector<int>space(M,0);
for(int i=0;i<M;i++) std::cin>>space[i];
std::vector<int>value(M,0);
for(int i=0;i<M;i++)std::cin>>value[i];
std::vector<std::vector<int>>dp(M+1,std::vector<int>(N+1,0));
//考虑物品1-i时,背包容量为j情况下,最大价值量
//dp[i][j] = std::max(dp[i-1][j-weight[i]] + value[i],dp[i-1][j]);
for(int i=1;i<=M;i++)
{
for(int j=1;j<=N;j++)
{
if(j>=space[i-1])
dp[i][j] = std::max(dp[i-1][j-space[i-1]] + value[i-1],dp[i-1][j]);
else
dp[i][j] = dp[i-1][j];
}
}
std::cout<<dp[M][N];
}
二维数组处理01背包特点是:
背包和物品的遍历顺序可以调换。
背包容量从小到大更好理解。
如果采用一维数组进行滚动呢?
int main()
{
int M=0; //材料数
int N=0; //背包容量
std::cin>>M>>N;
std::vector<int>space(M,0);
for(int i=0;i<M;i++) std::cin>>space[i];
std::vector<int>value(M,0);
for(int i=0;i<M;i++)std::cin>>value[i];
std::vector<int>dp(N+1,0);
// std::vector<std::vector<int>>dp(M+1,std::vector<int>(N+1,0));
//考虑物品1-i时,背包容量为j情况下,最大价值量
//dp[i][j] = std::max(dp[i-1][j-weight[i]] + value[i],dp[i-1][j]);
for(int i=1;i<=M;i++)
{
for(int j=N;j>=1;j--)
{
if(j>=space[i-1])
dp[j] = std::max(dp[j-space[i-1]] + value[i-1],dp[j]);
else
dp[j] = dp[j];
}
}
std::cout<<dp[N];
}
可以发现,这里最大的变化就是背包容量是从大到小遍历了。因为:数组的递推以来上一个物品和更小的重量。二维情况下是因为相关数据存储在了上一行,所以从小到大遍历。但是一维数组情况下,如果从小到大遍历将会覆盖上一个物品的数据。那么现在物品和背包容量的遍历顺序还可以调换吗?答案是不能!!!
滚动数组处理01背包特点是:
物品遍历在外,背包从大到小。
完全背包问题
如果一个物品可以添加无数次呢?应该怎么处理?
如果用二维数组解,那么内部还需要多一个对物品数量的for循环。
如果用滚动数组解呢?
这里直接给结论:物品、容量遍历顺序随意,但是容量需要从小到大遍历!
一般情况下,在手撕过程中不可能有时间推导这些,所以请记住两种背包的结论:
滚动数组01背包: 物品先容量后(必须), 容量从大到小(必须)
滚动数组完全背包:物品先容量后(非必须),容量从小到大(必须)
完全背包的排列组合问题
虽然一般情况下,完全背包的容量问题可以不考虑遍历顺序,但是如果是排列组合问题,就需要考虑遍历顺序了!
结论:
如果求组合,那么物品遍历在外(可以理解为物品加入的顺序是定的)
如果求排列,那么物品遍历在内(可以理解为物品多次循环,因此物品顺序不定)
代码随想录相关练习