动态规划--背包问题总结
背包问题(物品下标从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维优化
#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个物品,每个的价值和每个的体积和每个物品限制个数。
#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]);

浙公网安备 33010602011771号