背包问题汇总

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]

  • 放得下:

    1. 放:当前物品价值加上剩余重量在前i-1个物品中的最大值,f[i][j] = f[i - 1][j - v[i]] + w[i]
    2. 不放:相当于前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]

  • 放得下:

    1. 放:因为可以重复放,所以状态可以从本行进行继承,这样就可以实现对一个物品进行重复选择,同时不需要再与上一行进行取最大值因为,f[i][j]的状态转移方程中以对f[i-1][j]取过最大值,所以取当前物品价值加上剩余重量在前i个物品中的最大值,f[i][j] = f[i][j - v[i]] + w[i]
    2. 不放:相当于前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个

    1. 放:当前k个第i个物品价值加上剩余重量在前i-1个物品中的最大值,f[i][j] = f[i - 1][j - v[i] * k] + w[i] * k
    2. 不放:相当于前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]);
                }
            }
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值