混合背包,二维费用的背包问题,分组背包
混合背包
混合背包就是将01背包,完全背包,多重背包混合起来的背包问题。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
首先考虑01背包与完全背包的混合
考虑到在01背包和完全背包中的代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。伪代码如下:
for(i=1;i<=n;i++){
if 第i件物品只能取一次
for(int v=V;v>=w[i];v--)
dp[v]=max(dp[v],dp[v-w[i]]+v[i]);
else if 第i件物品可以取无限次
for(int v=w[i];v<=V;v++)
dp[v]=max(dp[v],dp[v-w[i]]+v[i]);
}
再加上多重背包
多重背包可以拆成01背包,我们通常用二进制来拆,将每种物品分成 l o g 2 n [ i ] log_2n[i] log2n[i]个物品,再套用上面的代码。复杂度O(V* l o g 2 N log_2N log2N)
具体代码如下:
//先将多重背包预处理
int cnt=1;
cin>>n>>V;
for(int i=1;i<=n;i++){
int m=1;
cin>>a>>b>>s;//a表示当前物品的代价,b表示价值,s表示数量(-1表示无穷)
if(s!=-1){
while(m<=s){
s-=m;
num[cnt]=1,w[cnt]=m*a,v[cnt++]=m*b;//分成用每次乘二表示的数打包成一件物品成一项加入数组
m*=2;
}
if(s>0){
num[cnt]=1,w[cnt]=s*a,v[cnt++]=s*b;//剩下的单成以项,加入数组
}
}
else
num[cnt]=s,w[cnt]=a,v[cnt++]=b
}
//再判断背包类型分别进行dp
for(i=1;i<=cnt;i++){
if(num[i]==1)
for(int j=V;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
else
for(int j=w[i];j<=V;j++)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
https://www.acwing.com/problem/content/7/ 混合背包模板题
二维费用的背包问题
二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为W和U。物品的价值为v[i]。
解题思路:
费用加了一维,只需状态也加一维即可。设dp[i][w][u]表示前i件物品付出两种代价分别为w和u时可获得的最大价值。
状态转移方程:
dp[i][w][u]=max(dp[i-1][w][u],dp[i-1][w-a[i]][u-b[i]]+v[i]) (01背包)
可以发现f[i][][]只与f[i-1][][]有关,如01背包所讲的降维优化,这里可以将三维转化为二维。
for(int i=1;i<=n;i++){
for(int w=V;w>=a[i];w--)
for(int u=U;u>=b[i];u--)
dp[w][u]=max(dp[w][u],dp[w-a[i]][u-b[i]]+v[i]);
}
以上是01背包的思路,同理可以推出完全背包和多重背包。
//完全背包
for(int i=1;i<=n;i++){
for(int w=a[i];w<=W;w++)
for(int u=b[i];u<=U;u++)
dp[w][u]=max(dp[w][u],dp[w-a[i]][u-b[i]]+v[i]);
}
//多重背包
//先将多重背包预处理
int cnt=1;
cin>>n>>W>>U;
for(int i=1;i<=n;i++){
int m=1;
cin>>w>>u>>v>>s;//w表示当前物品的代价1,u表示当前物品的代价2,v表示价值,s表示数量
while(m<=s){
s-=m;
a[cnt]=m*w,b[cnt]=m*u,c[cnt++]=m*v;//分成用每次乘二表示的数打包成一件物品成一项加入数组
m*=2;
}
if(s>0){
a[cnt]=s*w,b[cnt]=s*u,c[cnt++]=s*v;//剩下的单成以项,加入数组
}
}
//再用01背包进行dp
for(int i=1;i<=cnt;i++){
for(int v=V;v>=a[i];v--)
for(int u=U;u>=b[i];u--)
dp[w][u]=max(dp[w][u],dp[w-a[i]][u-b[i]]+v[i]);
}
物品总个数的限制
有时,“二维费用”的条件是以这样一种隐含的方式给出的:最多只能取M件物品。这事实上相当于每件物品多了一种“件数”的费用,每个物品的件数费用均为1,可以付出的最大件数费用为M。换句话说,设dp[w][m]表示付出费用w、最多选m件时可得到的最大价值,则根据物品的类型(01、完全、多重)用不同的方法循环更新,最后得到的dp[W][M]即为答案。
https://www.acwing.com/problem/content/8/ 二维费用的背包问题模板题
分组背包
问题:
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是v[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
思路:
这个问题变成了每组物品有两种策略:是选择本组的某一件,还是一件都不选。也就是说设dp[k][w]表示前k组物品花费费用w能取得的最大价值,则有:
dp[k][w]=max(dp[k-1][w],dp[k-1][w-c[i]]+v[i]|物品i属于第k组)
同样利用降维思想将二维转化为一维,使用一维数组的代码如下:
vector<int>num[N];//num[i]表示第i组有哪些物品
for(int i=1;i<=m;i++)//m为分组个数
for(int j=W;j>=0;j--)
for(int k=0;k<num[i].size();k++)
if(j>=c[num[i][k]])
dp[j]=max(dp[j],dp[j-c[num[i][k]]]+v[num[i][k]]);
注意这里的三层循环的顺序。 '‘for(int j=W;j>=w[i];j–)’'这一层循环必须在“for(int k=0;k<num[i].size();k++)”之外。这样才能保证每一组内的物品最多只有一个会被添加到背包中。
分组的背包问题将彼此互斥的若干物品称为一个组,这建立了一个很好的模型。不少背包问题的变形都可以转化为分组的背包问题(比如下次会讲到的有依赖的背包问题),由分组的背包问题进一步可定义“泛化物品”的概念,十分有利于解题。
https://www.acwing.com/problem/content/9/ 分组背包模板题
小结
混合背包就是将01背包,完全背包和多重背包混合在一起的背包问题。解题思路就是先将多重背包二进制拆分01背包,之后再遍历每个物品。根据物品类型选择正序dp(完全背包)或逆序dp(01背包)。伪代码如下:
for(i=1;i<=n;i++){
if 第i件物品只能取一次
逆序dp
else if 第i件物品可以取无穷次
顺序dp
else
先二进制分解再逆序dp
}
二维费用的背包问题就是增加物品的另一种费用,也就是在原来dp的基础上增加了一维,所以称为二维费用。解题思路就是在dp的时候多一重循环。实现代码如下:
for(int i=1;i<=n;i++){
for(int w=V;w>=a[i];w--)
for(int u=U;u>=b[i];u--)
dp[w][u]=max(dp[w][u],dp[w-a[i]][u-b[i]]+v[i]);
}
分组背包就是将物品分成多组,每组最多只能取一个的背包问题。其实分组背包可以看成是01背包进阶版,就是将01背包中的每个物品当成一个背包。在01背包中,我们以每一件物品作为动态规划的每一阶段,但是在分组背包中我们要以每一组作为每一阶段。
其实很简单,伪代码如下。
for(int i=1;i<=k;i++)
for(int c=v;c>=0;c--)
for( each 物品j in 第i组 )
if(c>=w[j])
f[c]=max(f[c],f[c-w[j]+v[j]]);
先枚举每一组,再枚举体积,最后枚举每组中的物品。记住顺序一定不能乱!