本文涉及到的背包问题
- 01背包。
- 完全背包。
- 多重背包。
- 混合背包。
- 01背包求解方案数。
- 完全背包求解方案数。
01背包
01背包应该很初级了。给定一个容量为v的背包,有n个物品,它们的重量和价值分别为a和b,且每件物品只有一个,问如何选择物品来装入背包,以使在不超过背包容量上限的情况下得到最大的价值。我们直接使用这道例题:
- 输入样例
70 3
71 100
69 1
1 2
- 输出样例
3
- AC Code
#include <iostream>
using namespace std;
const int maxn = 1005;
int m,t;
int a[maxn] = {0},b[maxn] = {0},f[maxn] = {0};
int main()
{
cin>>t>>m;
for(int i=1;i<=m;i++){
cin>>a[i]>>b[i];
}
for(int i=1;i<=m;i++){
for(int j=t;j-a[i]>=0;j--){
f[j] = max(f[j],f[j-a[i]]+b[i]);
}
}
cout<<f[t];
return 0;
}
可以看到代码十分的简短,01背包的模板如下:
for(int i=1;i<=m;i++){
for(int j=t;j-a[i]>=0;j--){
f[j] = max(f[j],f[j-a[i]]+b[i]);
}
}
用i来枚举每一件物品,用j来枚举背包中每一片空间。如果能放下,那么比较放和不放哪种情况得到的当前价值最大,以选择。状态转移方程:f[j] = max(f[j],f[j-a[i]]+b[i]);
。
01背包求解方案数
#include <iostream>
using namespace std;
const int maxn = 10005;
int n,m,f[maxn] = {0};
int main()
{
cin>>n>>m;
f[0] = 1;
for(int i=1;i<=n;i++){
int v;
cin>>v;
for(int j=m;j>=v;j--){
f[j] += f[j-v];
}
}
cout<<f[m];
return 0;
}
非常基础的01背包问题,没什么好说的。
完全背包
完全背包和01背包的题头并没有很大的变化,只是每一件物品可以取无限次。此时,将代码略微修改,即可得到完全背包的模板:
for(int i=1;i<=m;i++){
for(int j=a[i];j<=t;j++){
f[j] = max(f[j],f[j-a[i]]+b[i]);
}
}
👆可以看到,第二维变成for(int j=a[i];j<=t;j++)
,即,不是从背包的容量上限开始向下枚举,而是当前物品重量枚举到背包上限,这样可以确保尽可能多的物品放入背包。
完全背包求解方案数
拿一道例题来说明:
- AC Code
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 25005;
int a[maxn] = {0},f[maxn] = {0};
int t;
int main()
{
cin>>t;
while(t--){
memset(a,0,sizeof(a));
memset(f,0,sizeof(f));
int n,MAX = 0,ans = 0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
MAX = max(MAX,a[i]);
}
f[0] = 1;
for(int i=1;i<=n;i++){
for(int j=a[i];j<=MAX;j++){
f[j] += f[j-a[i]];
}
}
for(int i=1;i<=n;i++){
if(f[a[i]] == 1) ans++;
}
cout<<ans<<endl;
}
return 0;
}
- 解释:
〇思路:找到另一个可以替代当前货币系统的货币系统,不是让我们构造一个全新的货币系统,而是将原来货币系统中冗余的面额去掉。什么称作冗余的面额?即货币系统中某个面额可以被其他货币表示出来时,称它是冗余的。因此,新的货币系统的货币数应该是只有一种表示方案的货币。
①数据结构:用a来记录每个货币的面额,用f来记录当前的面额有多少种表示方法。
②初始化:memset(a,0,sizeof(a));memset(f,0,sizeof(f));
重复使用a和f,不将它们memset,第二次之后的结果一定出错。
③上限:即货币系统中面额的最大值。f[0] = 1
也是初始化,即面额为0的表示方案也只有一种,那就是它自己。
④求解方案数及输出答案:
👇同样是使用完全背包的思路。为什么使用完全背包?因为题目中表述:每种货币可以无限次使用。
for(int i=1;i<=n;i++){
for(int j=a[i];j<=MAX;j++){
f[j] += f[j-a[i]];
}
}
for(int i=1;i<=n;i++){
if(f[a[i]] == 1) ans++;
}
多重背包的二进制优化
多重背包不同于完全背包的“无限次使用”。多重背包指定了物品的个数,因此我们可以将多重背包看作是有多个物品的01背包,即,假设一件物品重量为v,价值为w,个数为s,我们可以将它看作是s个相同物品的01背包问题。但这样会使时间复杂度来到O(n×v×s),因此我们需要使用二进制来对多重背包进行优化。
我们直到,任何一个十进制数,都可以拓展为一个二进制数串,我们可以使用类似于快速幂的方法,来对多重背包进行优化。即,假设一件物品有13件,15可以拆分为1+4+8,那么这13件物品就变成了三捆,每捆分别有1、4、8个,对应的价值和重量也相应乘以1、4、8。
模板:
#include <iostream>
using namespace std;
const int maxn = 10005;
const int Lim = 105;
int v[maxn] = {0},w[maxn] = {0},f[maxn] = {0},n,m,cnt = 0;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){
int a,b,s;
cin>>a>>b>>s;
int k = 1;
while(k<=s){
v[++cnt] = k*a;
w[cnt] = k*b;
s -= k;
k *= 2;
}
if(s){
v[++cnt] = s*a;
w[cnt] = s*b;
}
}
for(int i=1;i<=cnt;i++){
for(int j=m;j>=v[i];j--){
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
}
cout<<f[m];
return 0;
}
混合背包
明白了多重背包、完全背包和01背包,求解混合背包问题就不难了。我们拿一道模板题来说:
#include <iostream>
using namespace std;
const int maxn = 1005;
int n,v;
int a[maxn] = {0},b[maxn] = {0},s[maxn] = {0},f[maxn] = {0};
int main()
{
cin>>n>>v;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i]>>s[i];
}
for(int i=1;i<=n;i++){
if(s[i] == 0){//完全背包
for(int j=a[i];j<=v;j++){
f[j] = max(f[j],f[j-a[i]]+b[i]);
}
}
else{//01背包与多重背包,都拆分成01背包来处理,多重背包用二进制优化
if(s[i] == -1) s[i] = 1;
for(int k=1;k<=s[i];k*=2){
for(int j=v;j>=k*a[i];j--){
f[j] = max(f[j],f[j-k*a[i]]+k*b[i]);
}
s[i] -= k;
}
if(s[i]){
for(int j=v;j>=s[i]*a[i];j--){
f[j] = max(f[j],f[j-s[i]*a[i]]+s[i]*b[i]);
}
}
}
}
cout<<f[v];
return 0;
}
分情况使用不同的背包,如果是多重背包,那么使用二进制优化将它转化为特殊的01背包,而01背包设置它的个数为1转化为只能使用一次的多重背包。