1.01背包
2.完全背包
3.多重背包
4.混合背包
5.分组背包
6.二维费用背包
有n个物品和一个容量为v的背包,每个物品的价值为c[i],体积为w[i],要求选择一些物品放入背包中,使物品总体积不超过m的前提下,物品的总价值最大,求最大总价值。
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。用子问题定义状态:即f[ i ][ j ]表示前 i 件物品恰放入一个容量为 j 的背包可以获得的最大价值。则其状态转移方程便是:
f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+c[i]);
/*
f[i-1][v]为不取第i件物品,让前i-1件物品来装v这么大的空间
f[i-1][v-w[i]]+c[i]为取第i件物品,让前i-1件物品来装v-w[i]这
么大的空间(因为要给第i件物品腾空间,所以是v-w[i]),然后加上
第i件物品的价值。
这里要注意,j要大于等于w[i]
*/
核心代码如下:
for(int i=1;i<=n;i++) {
for(int j=0;j<=m;j++) {
f[i][j]=f[i-1][j];
if(f[i-1][j-w[i]]+c[i]>f[i][j])
f[i][j]=f[i-1][j-w[i]]+c[i];
}
}
细细一品,其实我们可以用滚动数组来优化,因为我们可以发现第 i 个阶段的决策只与第 i - 1阶段有关,所以我们第一维只用开2;
核心代码:
int f[2][1005];
//第二维的大小就是v的范围,我这里是随便编得一个
for(int i=1;i<=n;i++) {
for(int j=0;j<=m;j++) {
f[i&1][j]=f[(i-1)&1][j];
if(f[(i-1)&1][j-w[i]]+c[i]>f[i&1][j])
f[i&1][j]=f[(i-1)&1][j-w[i]]+c[i];
}
}
再细细一品,在每个阶段的开始时,实际上执行了一次从f[ i-1 ][]到f[ i ][]的拷贝操作,于是我们就可以将二维改为一维。f[ j ]表示背包中放入总体积为 j 的物品的总价值。
核心代码:
for(int i=1;i<=n;i++) {
for(int j=m;j>=w[i];j--) {
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
注意,这里我们是用的倒序循环。循环到j时:
f数组中f[j~m]处于第i个阶段,f[0 ~j-1]处于第i-1个阶段。
当 j 逐渐减小时,满足我们的要求:总是用第i-1个阶段的状态向第i个阶段转移。
但如果我们用正序,则就会出现一个物品用多次的情况,不符合01背包。
有n个物品和一个容量为v的背包,每个物品的价值为c[i],体积为w[i],每个物品有无数件,要求选择一些物品放入背包中,使物品总体积不超过m的前提下,物品的总价值最大,求最大总价值。
一看题目,脑子第一反应的就是贪心,先排序,按c[i]/w[i]这个比值从大到小排序,然后来装。先让比值最大的来装,直到装不下为止,然后考虑第二件…
代码:
#include
using namespace std;
int n,m,ans;
struct node{
int w,c;
double bi;
}a[205];
bool cmp(node x,node y) {
return x.bi>y.bi;
}
int main() {
scanf("%d %d",&m,&n);
for(int i=1;i<=n;i++) {
scanf("%d %d",&a[i].w,&a[i].c);
a[i].bi=double(a[i].c)/double(a[i].w);
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++) {
while(a[i].w<=m) {
m-=a[i].w;
ans+=a[i].c;
}
}
printf("%d",ans);
return 0;
}
当然,有比较大的几率会TLE。
其实从01背包就发现了,当一维做法第二重循环为正序时,就有可能一件物品取多次了,完全符合完全背包的条件
核心代码:
for(int i=1;i<=n;i++) {
for(int j=w[i];j<=m;j++) {
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
一定要切记切记再切记,j的范围是m,不是n!(本蒟蒻在此处死惨了)
有n个物品和一个容量为v的背包,每个物品的价值为c[i],体积为w[i],每个物品有r[i]件,要求选择一些物品放入背包中,使物品总体积不超过m的前提下,物品的总价值最大,求最大总价值。
求解多重背包最直接的方法就是把第i种物品看作独立的r[i]件物品,转化为01背包问题来解答。
#include
using namespace std;
int n,m,w[105],c[105],r[105],f[105],ans;
int main() {
scanf("%d %d",&m,&n);
for(int i=1;i<=n;i++)
scanf("%d %d %d",&w[i],&c[i],&r[i]);
for(int i=1;i<=n;i++) {
for(int k=1;k<=r[i];k++) {
for(int j=m;j>=w[i];j--) {
f[j]=max(f[j],f[j-w[i]]+c[i]);
ans=max(ans,f[j]);
}
}
}
printf("%d",ans);
return 0;
}
这样的时间复杂度稍稍有一点高,我们可以用二进制拆分法来优化。
众所周知(你们知不知道我不知道,但我之前是不知道的):
代码:
#include
using namespace std;
int n,m,v,w[555],c[555],f[555];
int main() {
scanf("%d %d",&v,&m);
for(int i=1;i<=m;i++) {
int kongjian,jiage,shuliang;
scanf("%d %d %d",&kongjian,&jiage,&shuliang);
for(int k=1;k<=shuliang;k<<=1) {
n++;
w[n]=kongjian*k;
c[n]=jiage*k;
shuliang-=k;
}
if(shuliang!=0) {
n++;
w[n]=kongjian*shuliang;
c[n]=jiage*shuliang;
}
}
for(int i=1;i<=n;i++) {
for(int j=v;j>=w[i];j--) {
if(f[j-w[i]]+c[i]>f[j])
f[j]=f[j-w[i]]+c[i];
}
}
printf("%d",f[v]);
return 0;
}
有n类物品和一个容量为m的背包,每类物品的体积为wi,价格为ci,数量为ni(ni等于-1代表该物品只能用1次,ni等于0代表该物品可以使用无限件),问如何装取物品使得装入背包中的物品总价格最高?
我们可以发现,01背包是倒序循环,完全背包是正序循环,多重背包也是倒叙循环。这仨兄弟之间就差了顺序罢了。只用在第二重循环时判断一下需要正序还是倒序就完全可以了。
代码:
#include
using namespace std;
int m,n,v,c[10005],w[10005],f[1005];
bool s[10005];
int main() {
scanf("%d %d",&m,&v);
for(int i=1;i<=m;i++) {
int vi,pi,ni;
scanf("%d %d %d",&vi,&pi,&ni);
if(ni==-1) {
n++;
s[n]=1;
c[n]=pi;
w[n]=vi;
}
else if(ni==0) {
n++;
s[n]=0;
c[n]=pi;
w[n]=vi;
}
else {
for(int k=1;k<=ni;k<<=1) {
n++;
c[n]=k*pi;
w[n]=k*vi;
ni-=k;
s[n]=1;
}
if(ni) {
n++;
s[n]=1;
c[n]=ni*pi;
w[n]=ni*vi;
}
}
}
for(int i=1;i<=n;i++) {
if(s[i]) {
for(int j=v;j>=w[i];j--)
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
else {
for(int j=w[i];j<=v;j++)
f[j]=max(f[j],f[j-w[i]]+c[i]);
}
}
printf("%d",f[v]);
return 0;
}
给定N组物品,其中第i组物品有num[i]个物品。第i组的第j个物品的体积为vij,价值为pij。有一容积为V的背包,要求选择若干个物品放入背包,使得每组至多选择一个物品并且物品总体积不超过V的前提下,物品的价值总和最大。
这也可以看作一个01背包,只是需要多一个循环来看一看这一种物品中哪一个物品更好罢了。
代码:
#include
using namespace std;
int n,v,w[105][105],c[105][105],f[105],sum[105];
int main() {
scanf("%d %d",&n,&v);
for(int i=1;i<=n;i++) {
scanf("%d",&sum[i]);
for(int j=1;j<=sum[i];j++)
scanf("%d %d",&w[i][j],&c[i][j]);
}
for(int i=1;i<=n;i++) {
for(int j=v;j>=0;j--) {
for(int k=1;k<=sum[i];k++) {
if(j>=w[i][k])
f[j]=max(f[j],f[j-w[i][k]]+c[i][k]);
}
}
}
printf("%d",f[v]);
return 0;
}
我们先直接举个例子:潜水员。
潜水员有一定数量的气缸,每个气缸都有重量和气体容量。气缸同
时带有2种气体:一种为氧气,一种为氮气。潜水员为了完成潜水工作需要一定数量的氧气和氮气,允许他带多个气缸完成工作。 他所需气缸的总重量最少是多少?如果不能恰好带这么多气体,允许多
带一些(先考虑氧气最小,再考虑氮气最小)。
输入第一行有3整数u, v, n(1<=u, v<=1000),表示氧,氮各自需要的量, n(1<=n<=100)表示气缸的个数。
此后的n行,每行包括ai,bi,ci(1<=ai<=100,1<=bi<=100,1<=ci<=800),分别表示第i个气缸里的氧和氮的容量及汽缸重量。
因为允许多带一些,所以我们在循环的时候就得让多循环一些。加100就可以了(你多带一个就可以了,再多了也没用)。
代码:
#include
using namespace std;
int v,u,n,a[1005],b[1005],c[1005];
int f[1005][1005];
int main() {
scanf("%d %d %d",&v,&u,&n);
memset(f,0x3f3f3f3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
scanf("%d %d %d",&a[i],&b[i],&c[i]);
for(int i=1;i<=n;i++) {
for(int j=v+100;j>=a[i];j--) {
for(int k=u+100;k>=b[i];k--) {
f[j][k]=min(f[j][k],f[j-a[i]][k-b[i]]+c[i]);
}
}
}
for(int i=v;i<=v+100;i++) {
for(int j=u;j<=u+100;j++) {
if(f[i][j]!=0x3f3f3f3f) {
printf("%d\n",f[i][j]);
return 0;
}
}
}
printf("-1");
return 0;
}
好好品一品,相信你能明白的,感觉就想一个复杂一点点的01背包。