01背包问题
Posted 路人姜。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了01背包问题相关的知识,希望对你有一定的参考价值。
题目描述:
有n个重量和价值分别为wi,vi的物品。从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。(n>=1&&n<=100;wi,vi>=1&&wi,vi<=100;W>=1&&W<=10000)
输入:
4
2 3
1 2
3 42 25
输出:
7
分析:
首先确定两个定义,
①该问题的阶段是,物品种类为n,重量和价值分别为wi,vi。
②该问题的状态是,物品装还是不装入背包。
因为当前阶段的最优状态和上一个状态相关,而不管上一状态是最优状态还是最差状态,如输入输出的栗子。
阶段1)当n=4 ,W为0时,最大价值为0
2) ,W为1时,w1<=W,若将其装入背包则最大价值为v1即2,若不装入则最大价值为0,取最大,装入的情况,最大价值为2。
3) ,W为2时,w0<=W,若将其装入背包则最大价值为W-w0时的最大价值即阶段1的最大价值0加v0为3,若不装入则为阶段2)的最大价值2。
在阶段3)发现,在阶段3)时的状态,不管阶段1)阶段2)是什么状态都是由阶段1)和阶段2)直接得到的。
每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到,这个性质叫做最优子结构;而不管之前这个状态是如何得到的,这个性质叫做无后效性。所以才可以用动态规划来做。
写动态规划的一个方法(还有很多其他方法):将问题用记忆化搜索写出伪代码,推到处递推公式,然后就可以根据递推公式写出动态规划。
记忆化搜索的代码:
#include <iostream> #include <string.h> #define MAX_N 101 using namespace std; int n,W; int w[MAX_N],v[MAX_N]; int dp[MAX_N][MAX_N];//记忆化数组 int rec(int i,int j){ int res; //如果已经计算过,直接返回以前计算的值 if(dp[i][j]>=0){ return dp[i][j]; } //临界条件 if(i==n){ //已经没有剩余商品了 res=0; }//dp[n][j]=0 else if(j<w[i]){ res=rec(i+1,j); }//当j<w[i][j]时,dp[i][j]=dp[i+1][j]; else{ //装和不装两种情况都试一下 res=max(rec(i+1,j),rec(i+1,j-w[i])+v[i]); }//其他情况,dp[u][j]=max(dp(i+1,j),dp(i+1,j-w[i])+v[i]) //把每次计算过的值记录下来 dp[i][j]=res; return res; } int main() { cin>>n; for(int i=0;i<n;i++){ cin>>w[i]>>v[i]; } cin>>W; //记忆化数组别忘了初始化 memset(dp,-1,sizeof(dp)); cout<<rec(0,W); return 0; }
根据记忆化数组得到递推式:
dp[n][j]=0
当j<w[i][j]时,dp[i][j]=dp[i+1][j]
其他情况,dp[u][j]=max(dp(i+1,j),dp(i+1,j-w[i])+v[i])
动态规划的代码:
#include <iostream> using namespace std; int main() { int n,W; int dp[101][101],w[101],v[101]; cin>>n; for(int i=0;i<n;i++){ cin>>w[i]>>v[i]; } cin>>W; for(int i=n-1;i>=0;i--){ for(int j=0;j<=W;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]]+v[i]); } } } cout<<dp[0][W]; return 0; }
以上是关于01背包问题的主要内容,如果未能解决你的问题,请参考以下文章
代码随想录算法训练营第四十二天 | 01背包问题,你该了解这些01背包问题,你该了解这些 滚动数组 416. 分割等和子集