01背包:每个物品都只有1个
问题模型:n种物品,每种物品体积vi,价值wi,数量1,选择一些,放入容量为m的背包中,求最大价值
状态f[i][j]表示前i种物品放入背包中的最大值
边界:f[0][j] = 0,0 ≤ j ≤ m
目标:f[n][m]
分析:对于第i个物品,分两种情况讨论:
-
放不下:相当于前i-1个物品当前重量的最大值,f[i][j] = f[i - 1][j]
-
放得下:
- 放:当前物品价值加上剩余重量在前i-1个物品中的最大值,f[i][j] = f[i - 1][j - v[i]] + w[i]
- 不放:相当于前i-1个物品当前重量的最大值,f[i][j] = f[i - 1][j]
取最大值:f[i][j] = max(f[i - 1][j],f[i - 1][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 - v[i]] + w[i],f[i - 1][j]);
}else{
f[i][j] = f[i - 1][j]
}
}
}
考虑降维:因为第i行只与第i-1行有关,所以可以降维,因为每一个物品在取f[j-v[i]]时取的是第i-1行的,为了防止覆盖,j要倒推,因为j-v[i]<j
for(int i = 1;i <= n;i++){
for(int j = m;j >= v[i];j--){
f[j] = max(f[j],f[j - v[i]] + w[i]);
}
}
完全背包:每种物品都有无数个
问题模型:n种物品,每种物品体积vi,价值wi,数量,选择一些,放入容量为∞的背包中,求最大价值
状态:f[i][j]表示前i种物品放入背包中的最大值
边界:f[0][j] = 0,0 ≤ j ≤ m
目标:f[n][m]
分析:对于第i个物品,分两种情况讨论:
-
放不下:相当于前i-1个物品当前重量的最大值,f[i][j] = f[i - 1][j]
-
放得下:
- 放:因为可以重复放,所以状态可以从本行进行继承,这样就可以实现对一个物品进行重复选择,同时不需要再与上一行进行取最大值因为,f[i][j]的状态转移方程中以对f[i-1][j]取过最大值,所以取当前物品价值加上剩余重量在前i个物品中的最大值,f[i][j] = f[i][j - v[i]] + w[i]
- 不放:相当于前i-1个物品当前重量的最大值,f[i][j] = f[i - 1][j]
取最大值: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][j - v[i]] + w[i],f[i - 1][j]);
}else{
f[i][j] = f[i - 1][j]
}
}
}
考虑降维:因为第i行只与第i-1行有关,所以可以降维,因为每一个物品在取f[j-v[i]]时取的是第i行的,为了防止取到的是第i-1行,j要顺推 ,因为j-v[i]<j
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]);
}
}
多重背包:每种物品用p[i]个
问题模型:n种物品,每种物品体积vi,价值wi,数量pi,选择一些,放入容量为m的背包中,求最大价值
状态:f[i][j]表示前i种物品放入背包中的最大值
边界:f[0][j] = 0,0 ≤ j ≤ m
目标:f[n][m]
分析:对于第i个物品,分两种情况讨论:
-
放不下:相当于前i-1个物品当前重量的最大值,f[i][j] = f[i - 1][j]
-
放得下:枚举放入k个
- 放:当前k个第i个物品价值加上剩余重量在前i-1个物品中的最大值,f[i][j] = f[i - 1][j - v[i] * k] + w[i] * k
- 不放:相当于前i-1个物品当前重量的最大值,f[i][j] = f[i - 1][j]
取最大值:f[i][j] = max(f[i - 1][j],f[i][j - v[i] * k] + w[i] * k);
Tips:可以将多重背包想象为01背包,每个物品的重量是数量×原重量,价值是数量×原价值,数量通过枚举
状态转移方程:
for(int i = 1;i <= n;i++){
for(int j = 0;j <= m;j++){
for(int k = 0;k <= p[i];k++){//枚举选几个第i个物品,从0开始,因为可以不选
if(j >= k * v[i]){
f[i][j] = max(f[i][j]/*必须是f[i][j],不然就不会去最大值了*/,f[i - 1][j - k * v[i]] + k * w[i]);//取最大值
}else{
break;//滑动窗口,降时间复杂度
}
}
}
}
考虑降维:因为第i行只与第i-1行有关,所以可以降维,但j要倒推(原因与01背包一样)
for(int i = 1;i <= n;i++){
for(int j = m;j >= 0;j--){
for(int k = 0;k <= p[i];k++){//枚举选几个第i个物品,从0开始,因为可以不选
if(j >= k * v[i]){
f[j] = max(f[j],f[j - k * v[i]] + k * w[i]);//取最大值
}else{
break;//滑动窗口,降时间复杂度
}
}
}
}
也可以写为:
for(int i = 1;i <= n;i++){
for(int j = m;j >= 0;j--){
for(int k = 0;k <= min(p[i],j / v[i])/*将判断移到循环中*/;k++){//枚举选几个第i个物品,从0开始,因为可以不选
f[j] = max(f[j],f[j - k * v[i]] + k * w[i]);//取最大
}
}
}
恰填满背包的方案数
状态:f[i][j]表示前i种物品恰填满容量为j的背包中的值
边界:f[0][j] = 0,1 ≤ j ≤ m,f[0][0] = 1;
目标:f[n][m]
for(int i = 1;i <= n;i++){
for(int j = m;j >= v[i];j--){
f[j] += f[j - v[i]];
}
}
for(int i = 1;i <= n;i++){
for(int j = v[i];j <= m;j++){
f[j] += f[j - v[i]];
}
}
for(int i = 1;i <= n;i++){
for(int j = m;j >= 0;j--){
for(int k = 1;k <= p[i];k++){
if(j >= k * v[i]){
f[j] += f[j - k * v[i]];
}else{
break;
}
}
}
}
填满背包的最小物品数
状态:f[i][j]表示前i种物品填满容量为j的背的最小物品数
边界:f[0][j] =∞ ,1 ≤ j ≤ m,f[0][0] = 0;
目标:f[n][m]
for(int i = 1;i <= n;i++){
for(int j = m;j >= v[i];j--){
f[j] = min(f[j],f[j - v[i]] + 1);
}
}
for(int i = 1;i <= n;i++){
for(int j = v[i];j <= m;j++){
f[j] = min(f[j],f[j - v[i]] + 1);
}
}
for(int i = 1;i <= n;i++){
for(int j = m;j >= 0;j--){
for(int k = 0;k <= p[i];k++){
if(j >= k * v[i]){
f[j] = min(f[j],f[j - k * v[i]] + k);
}else{
break;
}
}
}
}
分组背包
问题模型:一共有n种,每组有一些物品,每个物品有体积vi,价值wi,放入容量为m的背包中,求最大价值
最多选一个:
for(int i = 1;i <= t;i++){//组数
for(int j = 0;j <= m;j++){//容量
f[i][j] = f[i - 1][j];
for(int k = 0;k < v[i].size();k++){//第i组第k个
if(j >= v[i][k]){
f[i][j] = max(f[i][j],f[i - 1][j - v[i][k]] + w[i][k]);
}
}
}
}
降维:
for(int i = 1;i <= t;i++){//组数
for(int j = m;j >= 0;j--){//容量
f[i][j] = f[i - 1][j];
for(int k = 0;k < v[i].size();k++){//第i组第k个
if(j >= v[i][k]){
f[j] = max(f[j],f[j - v[i][k]] + w[i][k]);
}
}
}
}