动态规划--背包问题总结
背包问题(物品下标从1开始)
0~1背包模版:
题目一般是要求:
给定一个容量给定的背包(就是物品体积和小于容量),然后有N个物品,每个的价值和每个的体积,然后每个物品能用1次或0次求最大价值。
方法步骤:
(1)状态表示:一般用二维数组 f[i][j]表示从前i个物品选体积小于等于j的最大价值。
(2)状态转移方程:
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);//两部分取最大值
意思是f[i][j]包括两部分:
1.f[i-1][j]背包里不包括第i件物品,就是到前i-1体积就达到j,此时价值为f[i-1][j]。
2.f[i-1][j-v[i]]+w[i] 背包里包括第i个物品,也就是到第i-1件物品体积达到j-v[i],此时价值为f[i-1][j-v[i]]+w[i]
还有注意事项:
第一部分一定可以有
第二部分即包含第i个物品需要j>v[i]才能放第i个物品
方法一:用二维dp做0~1背包
查看代码
#include<bits/stdc++.h>
using namespace std;
//f数组(动态规划数组dp)
int f[1200][1200];
//体积数组
int v[1200];
//价值数组
int w[1200];
int main(){
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
f[i][j]=f[i-1][j];
//能放得下v[i]
if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
}
}
cout<<f[N][V];
return 0;
}
方法二:用一维dp进行优化
查看代码
#include<bits/stdc++.h>
using namespace std;
//f数组(动态规划数组dp)
int f[1200];
//体积数组
int v[1200];
//价值数组
int w[1200];
int main(){
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=N;i++){
for(int j=V;j>=v[i];j--){
//能放得下v[i]
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[V];
return 0;
}
二维dp优化成一维dp的方法:
消除第一维,然后改写内层for
for(int j=V;j>=v[i];j--)从大到小
完全背包:
与0~1背包相比区别是:
每件物品可以选0~多件
查看代码
#include<bits/stdc++.h>
using namespace std;
int v[1200];
int w[1200];
int dp[1200][1200];
int main(){
int N,V;
cin>>N>>V;//N件物品,背包容量为V
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
for(int k=0;k*v[i]<=j;k++){
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
}
}
}
cout<<dp[N][V];
return 0;
}
核心代码
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
for(int k=0;k*v[i]<=j;k++){
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
}
}
}
最外层遍历物品下标(从1开始),中层循环遍历体积,内层遍历第i种物品数
状态转移方程:
dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k);
//
包括第i种物品:
第i种物品可以有0~多件(0~k)
优化上面方案
查看代码
#include<bits/stdc++.h>
using namespace std;
int v[1200];
int w[1200];
int dp[1200][1200];
int main(){
int N,V;
cin>>N>>V;//N件物品,背包容量为V
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i];
}
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
dp[i][j]=dp[i-1][j];
if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
}
cout<<dp[N][V];
return 0;
}
优化方案:
原来:
dp[i][j]=max(dp[i-1][j-k*v[i]]+k*w[i]);
展开:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]]+2*w[i],dp[i-1][j-3*v[i]]+3*w[i]...);
展开:dp[i][j-v[i]]=max(dp[i-1][j-v[i]],dp[i-1][j-2*v[i]]+w[i],dp[i-1][j-3*v[i]]+2*w[i]...)
所以状态转移优化方程:dp[i][j]=max(dp[i-1][j],dp[i][j-v[i]]+w);
对上述代码进行1维优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include<bits/stdc++.h> using namespace std; int v[1200]; int w[1200]; int dp[1200]; int main(){ int N,V; cin>>N>>V; //N件物品,背包容量为V for ( int i=1;i<=N;i++){ cin>>v[i]>>w[i]; } for ( int i=1;i<=N;i++){ for ( int j=v[i];j<=V;j++){ dp[j]=max(dp[j],dp[j-v[i]]+w[i]); } } cout<<dp[V]; return 0; } |
0~1背包和完全背包总结
状态转移方程相同:
dp[j]=max(dp[j],dp[j-v[i]]+w[i])
外层循环i是1~n种物品
区别:
题意不同
0~1背包:每个物品有0~1件
完全背包:每个物品有0~多件
内层循环:
0~1背包:j从V到 v[i]
完全背包:j从v[i]到V
多重背包:
题意要求一般是:
给定一个容量给定的背包(就是物品体积和小于容量),然后有N个物品,每个的价值和每个的体积和每个物品限制个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include<bits/stdc++.h> using namespace std; int v[110]; int w[110]; int s[110]; int dp[110][110]; int main(){ int N,V; cin>>N>>V; for ( int i=1;i<=N;i++)cin>>v[i]>>w[i]>>s[i]; for ( int i=1;i<=N;i++){ for ( int j=0;j<=V;j++){ for ( int k=0;k<=s[i]&&j-k*v[i]>=0;k++){ dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]); } } } cout<<dp[N][V]; return 0; } |
对多重背包的一维优化
查看代码
#include<bits/stdc++.h>
using namespace std;
int v[11010];
int w[11010];
int dp[11010];
int main(){
int N,V;
cin>>N>>V;
int a,b,s;
int cnt=0;
for(int i=1;i<=N;i++){
cin>>a>>b>>s;//v,w,s
int k=1;
while(k<=s){
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
s-=k;
k*=2;
}
if(s){
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
int n=cnt;
for(int i=1;i<=n;i++){
for(int j=V;j>=v[i];j--){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
}
}
cout<<dp[V];
return 0;
}
总结:
方法用的是合并
a,b,s 为当前品种的体积价值和数量限制
k:1,2,4,8
然后s不断减k,k为当前件数
最后剩余的s再乘上当前体积价值
还要cnt++
最后int n=cnt
数组一般开多大:
向上取整(log2背包容量大小)+10;
分组背包问题:
vij表示第i种物品中的第j个物品的体积
wij表示第i种物品中的第j个物品的价值
查看代码
#include<bits/stdc++.h>
using namespace std;
int v[110][110];
int w[110][110];
int s[110];
int dp[110];
int main(){
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++){
cin>>s[i];
//每个品种的个数
for(int j=0;j<s[i];j++){
cin>>v[i][j]>>w[i][j];
}
}
for(int i=1;i<=N;i++ ){
for(int j=V;j>=0;j--){
for(int k=0;k<s[i];k++){
if(j-v[i][k]>=0)
dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]) ;
}
}
}
cout<<dp[V];
return 0;
}
代码分析:
1.物种下标从1开始,每个物种里的物品下标从0开始,即i从0开始,j从1开始
for(int i=1;i<=N;i++){
cin>>s[i];
//每个品种的个数
for(int j=0;j<s[i];j++){
cin>>v[i][j]>>w[i][j];
}
}
2.状态转移方程
dp[j ]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
然后三层循环
外:遍历1~N个物种
中:遍历体积[V,0]
内:0=<k<s[i]
条件:j-v[i][k]>=0
因为下标从0开始
背包 总结(一维数组)
- 0~1背包:(每件物品装入0~1件)
体积j从V到v[i]
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
- 完全背包:(每件物品0~多件)
体积j从v[i]到V
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
- 多重背包(每件物品0~限制的件数)
体积j从V到v[i]
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
- 分组背包(多个组,每组里面又有很多物品,每个物品的体积,价值输入。
体积 j从V到0
条件:j-v[i][k]>=0
dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具