前言:这个问题是建立在01背包的基础之上的,如果没听过01背包的小伙伴可以先前往我的上一篇文章中进行学习。接下来要学习的是背包系列中的完全背包
完全背包同样也是动态规划算法中很经典的模板题,题目描述如下:
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,n,m,用空格隔开,分别表示物品种数和背包容积。
接下来有 n 行,每行两个整数 v[i],w[i],用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<n,m≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
10
这里很明显可以发现,01背包的代码已经不能解决这个问题,那我们先考虑,我们面对每一种物品时的选择:选/不选,选几个?
想到这里,我们就可以结合01背包的代码,得到完全背包的朴素算法,代码如下:
#include<bits/stdc++.h>
using namespace std;
int f[1005],v[1005],w[1005],n,m;
int main(){
int i,j,k;
cin>>n>>m;
for(i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(i=1;i<=n;i++){//遍历n个物品
for(j=n;j>=v[i];j--){//倒着枚举体积
for(k=1;k*v[i]<=j;k++){//枚举选几个
f[j]=max(f[j],f[j-v[i]*k]+w[i]*k);
//状态转移(第i种物品要占用k*v[i]的体积,产生k*w[i]的价值)
}
}
}
cout<<f[m];
return 0;
}
但是看到数据范围,再加上我们要处理实际问题,肯定是不能用三重循环的,这时候就要想办法精简我们的代码,提升算法效率。我们先写下状态转移方程:
f[j]=max(f[j],f[j-v[i]]+w[i],f[j-2*v[i]]+2*w[i],......,f[j-k*v[i]]+k*w[i]);
完成一次操作f[j]=max(f[j],f[j-v[i]]+w[i])后方程变为:
f[j]=max(f[j],f[j-v[i]]+w[i],f[j-2*v[i]]+2*w[i],......,f[j-(k-1)*v[i]]+(k-1)*w[i]);
再完成一次操作f[j]=max(f[j],f[j-v[i]]+w[i])后方程变为:
f[j]=max(f[j],f[j-v[i]]+w[i],f[j-2*v[i]]+2*w[i],......,f[j-(k-2)*v[i]]+(k-2)*w[i]);
再完成一次操作f[j]=max(f[j],f[j-v[i]]+w[i])后方程变为:
f[j]=max(f[j],f[j-v[i]]+w[i],f[j-2*v[i]]+2*w[i],......,f[j-(k-3)*v[i]]+(k-3)*w[i]);
以此类推,最终状态转移方程又会变成f[j]=max(f[j],f[j-v[i]]+w[i]);
由此我们可以得到,只要把01背包中的枚举体积改为正序即可完成完全背包问题,代码如下:
#include<bits/stdc++.h>
using namespace std;
int f[1005],v[1005],w[1005],n,m;
int main(){
int i,j,k;
cin>>n>>m;
for(i=1;i<=n;i++){
cin>>v[i]>>w[i];
}
for(i=1;i<=n;i++){//遍历n种物品
for(j=v[i];j<=m;j++){//正着枚举物品总体积
f[j]=max(f[j],f[j-v[i]]+w[i]);//状态转移方程
}
}
cout<<f[m];
return 0;
}
如果学懂了的小伙伴们可以尝试一下洛谷P1616 采药(几乎纯模板):
疯狂的采药 - 洛谷https://www.luogu.com.cn/problem/P1616最后希望大家能够喜欢我的文章,多多支持我,有问题的小伙伴可以私信交流,大家共同进步!