渣渣又来啦,接上一篇的背包问题。
1.混合三种背包问题
问题简述:如果将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),应该如何求解。
问题分析:
01背包与完全背包的混合:
考虑到在01背包与完全背包中最后给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可。
伪代码如下:
for i=1..N
if 第i件物品是01背包
for v=V..0
f[v]=max{f[v],f[v-w[i]]+c[i]};
else if 第i件物品是完全背包
for v=0..V
f[v]=max{f[v],f[v-w[i]]+c[i]};
例题:混合背包
问题描述:一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别为C1,C2,…,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。求解将哪些物品装入背包可以使这些物品的费用总和不超过背包容量,且价值总和最大。
【输入格式】
第1行:两个整数,M(背包容量,M<=200),N(物品数量,N<=30);
第2至N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件;若为其他数字,则为此物品可购买的最多件数。
【输出格式】
输出仅一行,一个数,表示最大总价值。
【输入样例】 【输出样例】
10 3 11
2 1 0
3 3 1
4 5 4
#include<bits/stdc++.h>
using namespace std;
int m,n;
int w[31],c[31],p[31];
int f[201];
int max(int x,int y)
{
if(x<y) return y;
else return x;
}
int main()
{
cin>>m>>n;
for(int i=1;i<=n;i++)
cin>>w[i]>>c[i]>>p[i];
for(int i=1;i<=n;i++)
{
if(p[i]==0)//完全背包
{
for(int j=w[i];j<=m;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
else
{
for(int j=1;j<=p[i];j++)//01背包和多重背包
for(int k=m;k>=w[i];k--)
f[k]=max(f[k],f[k-w[i]]+c[i]);
}
}
cout<<f[m]<<endl;
return 0;
}
2.二维费用的背包问题
问题简述:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值分别为V和U。物品的价值为c[i]。
算法:费用加了一堆,只需状态也加一维即可。设f[i][v][u]表示前i件物品付出两种代价分别为v和u时可获得的最大价值。
状态转移方程就是:
f[i][v][u]=max{f[i-1][v][u],f[i-1][v-a[i]][u-b[i]]+c[i]}
例题:潜水员
问题描述:潜水员为了潜水要使用特殊装备。他有一个带2种气体的气缸:一个为氧气,一个为氮气。让潜水员下潜的深度需要各种的数量的氧和氮。潜水员有一定数量的气缸。每个气缸都有重量和气体容量。潜水员为了完成他的工作需要特定数量的氧和氮。它完成工作所需气缸的总重的最低限度的时多少?
【输入格式】
第1行有2个整数m,n(1<=m<=21,1<=n<=79).它们表示氧和氮各自需要的量。
第2行为整数k(1<=k<=1000)表示气缸的个数。
此后的k行,每行包括ai,bi,ci,(1<=ai<=21,i<=bi<=79,1<=ci<=800)3个整数。这些各是:第i个气缸里的氧和氮的容量及气缸重量。
【输出格式】
仅一行,包含一个整数,为潜水员完成工作所需的气缸的重量总和的最低值。
【输入样例】 【输出样例】
5 60 5 249
3 36 120
10 25 129
5 50 250
1 45 130
4 20 119
#include<bits/stdc++.h>
using namespace std;
int v,u,k;
int a[1001],b[1001],c[1001];
int f[101][101];
int main()
{
memset(f,127,sizeof(f));
f[0][0]=0;
cin>>v>>u>>k;
for(int i=1;i<=k;i++)
cin>>a[i]>>b[i]>>c[i];
for(int i=1;i<=k;i++)
for(int j=v;j>=0;j--)
for(int l=u;l>=0;l--)
{
int t1=j+a[i],t2=l+b[i];
if(t1>v) t1=v;//若氮、氧含量超过需求,可直接用需求量代换
if(t2>u) t2=u;//不影响最优解
if(f[t1][t2]>f[j][l]+c[i]) f[t1][t2]=f[j][l]+c[i];
}
cout<<f[v][u]<<endl;
return 0;
}
3.分组的背包问题
问题简述:有N件物品和一个容量为V的背包。第i件物品的费用是w[i],价值是c[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
算法:这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有f[k][v]=max{f[k-1][v],f[k-1][v-w[i]]+c[i]|物品i属于第k组}。
例题:分组背包
问题描述:一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,…,Wn,它们的价值分别是C1,C2,…,Cn。这些物品别划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量且价值总和最大。
【输入格式】
第1行:三个整数,V(背包容量,V<=200),N(物品数量,N<=30)和T(最大组号,T<=10);
第2至N+1行:每行三个整数Wi,Ci,P,表示每个物品的重量,价值,所属组号。
【输出格式】
仅一行,一个数,表示最大总价值。
【输入样例】 【输出样例】
10 6 3 20
2 1 1
3 3 1
4 8 2
6 9 2
2 8 3
3 9 3
#include<bits/stdc++.h>
using namespace std;
int v,n,t;
int w[31],c[31];
int a[11][32],f[201];
int main()
{
cin>>v>>n>>t;
for(int i=1;i<=n;i++)
{
int p;
cin>>w[i]>>c[i]>>p;
a[p][++a[p][0]]=i;
}
for(int k=1;k<=t;k++)
for(int j=v;j>=0;j--)
for(int i=1;i<=a[k][0];i++)
if(j>=w[a[k][i]])
{
int tmp=a[k][i];
if(f[j]<f[j-w[tmp]]+c[tmp])
f[j]=f[j-w[tmp]]+c[tmp];
}
cout<<f[v]<<endl;
return 0;
}
4.背包问题的方案总数
对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖等)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数。
对于这类改变问法的问题,一般只需将状态转移方程中的max改成sum即可。
例题:货币系统
问题描述:给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
【输入样例】 【输出样例】
3 10 10
1
2
5
算法1
#include<bits/stdc++.h>
using namespace std;
int m,n;
int a[1001];
long long f[10001];//注意要用long long
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
f[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)
for(int k=1;k<=j/a[i];k++)
f[j]+=f[j-k*a[i]];
cout<<f[m]<<endl;
return 0;
}
算法2
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[101];
long long f[10001];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
f[0]=1;
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j++)
f[j]+=f[j-a[i]];
cout<<f[m]<<endl;
return 0;
}