动态规划——完全背包问题
完全背包问题一般是指:
有N件物品和一个能背重量为W的背包,第i件物品的重量为weight[i],价值为value[i]。每件物品有无限个(也就是可以放入背包多次),求怎样可以使背包物品价值总量最大。
完全背包与01背包问题的区别在于01背包物品只有一个,完全背包有无数个。
1、原始朴素算法:
dp[i][j] 的状态表示:①集合:表示前i个物品中,总体积不超过j的选法的物品价值最大值 ②属性:当前情况下的最大值
dp[i][j] 的状态计算:当前的第i个物品,可以选任意多个(总体积不超过背包容量)。那么就枚举呗,从选0个到选k个枚举,dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i],dp[i-1][j-2*weight[i]]+2*value[i],......,dp[i-1][j-k*weight[i]]+k*value[i]) (※)
朴素代码:
#include<iostream> using namespace std; int main(){ int n,cap; cin>>n>>cap; int *v=new int[n]; int *w=new int[n]; for(int i=0;i<n;i++){ cin>>v[i]>>w[i]; } int **dp=new int*[n+1]; for(int i=0;i<=n;i++){ dp[i]=new int[cap+1]; } for(int i=1;i<=n;i++){ for(int j=1;j<=cap;j++){ for(int k=0;k*v[i-1]<=j;k++){ dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i-1]]+k*w[i-1]); } } } cout<<dp[n][cap]; return 0; }
朴素算法有三重循环,我们现在将其优化成二重循环,原理如下:
上面的(※)式:dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i],dp[i-1][j-2*weight[i]]+2*value[i],......,dp[i-1][j-k*weight[i]]+k*value[i])
则dp[i][j-weight[i]]可按此公式推得(※1):dp[i][j-weight[i]]=max(dp[i-1][j-weight[i]],dp[i-1][j-2*weight[i]]+value[i],dp[i-1][j-3*weight[i]]+2*value[i],......,dp[i-1][j-k*weight[i]]+(k-1)*value[i])
可以观察到,(※)的加粗(下划线)部分,和(※1)大致相同,只是(※)的加粗部分每项多加一个value[i]。也就是说(※)加粗部分的最大值,比(※1)的最大值多了一个value[i]。
那么便可以把(※1)带入(※)的加粗部分中,让表达式更加简洁一些,(※)改进得:
dp[i][j]=max(dp[i-1][j],dp[i][j-weight[i]]+value[i]) (核心)
2、优化成双层循环的算法:
#include<iostream> using namespace std; int main(){ int n,cap; cin>>n>>cap; int *v=new int[n]; int *w=new int[n]; for(int i=0;i<n;i++){ cin>>v[i]>>w[i]; } int **dp=new int*[n+1]; for(int i=0;i<=n;i++){ dp[i]=new int[cap+1]; } for(int i=1;i<=n;i++){ for(int j=1;j<=cap;j++){ dp[i][j]=dp[i-1][j]; if(j>=v[i-1])dp[i][j]=max(dp[i][j],dp[i][j-v[i-1]]+w[i-1]); } } cout<<dp[n][cap]; return 0; }
3、继续优化成一维dp数组的算法:
#include<iostream> using namespace std; int main(){ int n,cap; cin>>n>>cap; int *v=new int[n]; int *w=new int[n]; for(int i=0;i<n;i++){ cin>>v[i]>>w[i]; } int *dp=new int[cap+1]; for(int i=1;i<=n;i++){ for(int j=v[i-1];j<=cap;j++){ dp[j]=max(dp[j],dp[j-v[i-1]]+w[i-1]); } } cout<<dp[cap]; return 0; }
4、小结:
优化成一维的与01背包的状态转移方程对比,仅仅微小的区别,但是背后含义千差万别:
01背包:dp[i][j]= max(dp[i-1][j],dp[i-1][j-w]+v)
完全背包:dp[i][j]=max(dp[i-1][j],dp[i][j-w]+v)
这个微小的区别,导致了将dp数组优化至一维空间时,01背包是从后向前遍历(防止左边的数,即含义上是上一层的数被修改覆盖),完全背包是从前向后遍历(需要这一层前面的数)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人