简单背包
问题模型
n种物品,每种物品有1个,
放入容量为m的背包中,每个物品有体积v[i]和价值w[i]
求放入背包的最大价值
基本方法
- n表示物品总数,m表示背包容量,v[]表示物品体积,w[]表示物品价值
- f[i][j]表示将前i个物品放入容量为j的背包能获得的最大价值
- f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i-1][j]);
- 边界:f[0][j]=0
- 解:f[n][m]
降维
| 0 | 1 | 2 | ... | <-i | |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 1 | x | x | x | x | |
| 2 | x | x | x | x | |
| 3 | x | x | x | x | |
| ... | ... | ... | ... | ... | |
| j |
参考代码:
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
if(f[j-v[i]]+w[i]>f[j])
f[j]=f[j-v[i]]+w[i];
}
}
问题模型
n种物品,每种物品有无数个,
放入容量为m的背包中,每个物品有体积\(v[i]\)和价值\(w[i]\)
求放入背包的最大价值
方法1
伪代码:
//边界是f[0][j]=0
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=j/v[i];j++){
f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
}
}
}
//解是f[n][m]
时间复杂度很高!
方法2
边界和解都一样
f[i][j]的定义还一样
此时考虑不选或者至少选一个:
- 第二种情况,如果真的某个物品只选1个会不会出问题呢?
综上所述,f[i][j]=\max(f[i-1][j],f[i][j-v[i]]+w[i]);
伪代码:
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
if(j>=v[i])
f[i][j]=max(f[i-1][j]/*不选*/,f[i][j-v[i]]+w[i]/*选至少一个*/);
else
f[i][j]=f[i-1][j];
}
}
}
降了一层循环,真NICE!
| 0 | 1 | 2 | ... | <-i | |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 1 | x | x | x | x | |
| 2 | x | x | x | x | |
| 3 | x | x | x | x | |
| ... | ... | ... | ... | ... | |
| j |
降维
研究表格,发现可以降维:
f[j]=max(f[j],f[j-v[i]]+w[i]);
参考代码:
for(int i=1;i<=n;i++){
for(int j=v[i];j<=m;j++){
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
}
问题模型
n种物品,每种物品有c[i]个,
放入容量为m的背包中,每个物品有体积\(v[i]\)和价值\(w[i]\)
求放入背包的最大价值
基本方法
f[i][j]的定义不变
c[i]表示第i个物品有多少个
伪代码:
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=min(c[i],j/v[i]);k++){
f[i][j]=max(f[i-1][j-k*v[i],k*w[i]);
}
}
}
降维
| 0 | 1 | 2 | ... | <-i | |
|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | |
| 1 | x | x | x | x | |
| 2 | x | x | x | x | |
| 3 | x | x | x | x | |
| ... | ... | ... | ... | ... | |
| j |
参考代码:
for(int i=1;i<=n;i++){
for(int j=m;j>=0;j--){
for(int k=0;k<=min(c[i],j/v[i]);k++){
if(f[j-k*v[i]]+k*w[i]>f[j])
f[j]=f[j-k*v[i]]+k*w[i];
}
}
}
解法二——转01背包
第i种物品有c[i]个,可以转化成c[i]中物品,每种物品有\(1\)个
这样将多重背包转成了01背包
参考代码:
for(int i=1;i<=n;i++){
for(int k=1;k<=c[i];k++){//k从1开始,易错
for(int j=m;j>=v[i];j--){
if(f[j-v[i]]+w[i]>f[j])
f[j]=f[j-v[i]]+w[i];//注意这里是01背包的状态转移方程
}
}
}
有些物品只能选1个,有些物品只能选c[i]个,有些物品能选无数个
因为所有的背包的边界、解和定义都一样,所以可以根据具体的数量规定具体的状态转移方程
参考代码:
//c[i]=0表示有无数个
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
if(c[i]==1){//01背包
if(j>=c[i])
f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
else
f[i][j]=f[i-1][j];
}
else if(c[i]==0){//完全背包
if(j>=c[i])
f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
else
f[i][j]=f[i-1][j];
}
else{//混合背包
for(int k=0;k<=min(c[i],j/v[i]);k++)
f[i][j]=max(f[i-1][j],f[i-1][j-k*v[i]]+k*w[i]);
}
}
}
降维也要根据不同的背包问题,选择不同的方式
//c[i]=0表示有无数个
for(int i=1;i<=n;i++){
if(c[i]==1){//01背包
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
else if(p[i]==0){//完全背包
for(int j=v[i];j<=m;j++)
f[j]=max(f[j],f[j-v[i]]+w[i]);
}
else{
for(int j=m;j>=0;j--){//多重背包
for(int k=0;k<=min(c[i],j/v[i]);k++)
f[j]=max(f[j],f[j-k*c[i]]+k*w[i]);
}
}
}
第二种方式
其实,因为背包容量最大也只能是m,所以理论上能选无数个的物品最多也只能选m/v[i]个
01背包可以看成c[i]=1的多重背包
这样,我们可以将三种背包都变成多重背包
模板代码略
- 对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数
- 对于这类问题,一般只需要将状态转移方程中的max改成sum,再将边界稍作修改即可
基本方法
f[i][j]表示将前i件物品恰填满容量为j的背包的方案总数
- 01背包的状态转移方程是$f[i][j]=f[i-1][j]+f[i-1][j-v[i]];
- 完全背包的状态转移方程是$f[i][j]=f[i-1][j]+f[i][j-v[i]];
- 多重背包的状态转移方程是$f[i][j]=\sum_{k=0}^{\min(c[i],j/v[i])}f[i-k\cdot v[i]];
- 边界:f[0][0]=1
思考
- 在01背包达到最优解时,输出最优解中所有选中物品的编号
参考代码:
#include <iostream>
#include <cstdio>
#define MAXN 10010
#define MAXM 10010
//分开定义MAXN和MAXM表示这个数组的大小跟n和m中的哪一个有关
using namespace std;
int n,m,v[MAXN],w[MAXN],f[MAXM];
bool g[MAXN][MAXM];
void DFS(int x,int y){
if(x==0)
return;
if(g[x][y]){
DFS(x-1,y-v[x]);
printf("%d ",x);
}
else{
DFS(x-1,y);
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d %d",&v[i],&w[i]);
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){//降维
if(f[j-v[i]]+w[i]>f[j]){
f[j]=f[j-v[i]]+w[i];
g[i][j]=true;
}
}
}
printf("物品最大价值:%d\n选的物品:",f[m]);
DFS(n,m);//挨个输出物品
return 0;
}
- 对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的最小物品数量
基本方法
f[i][j]表示将前i件物品恰填满容量为j的背包的最小物品数
- 01背包的状态转移方程是f[i][j]=\min(f[i-1][j],f[i-1][j-v[i]]+1);
- 完全背包的状态转移方程是f[i][j]=\min(f[i-1][j],f[i][j-v[i]]+1);
- 多重背包的状态转移方程是f[i][j]=\min(f[i][j],f[i-1][j-k*v[i]]+k);其中0 <= k <= min(c[i],j/v[i])
- 边界:f[0][i]=0x3f3f3f3f,f[0][0]=1
浙公网安备 33010602011771号