几种背包问题总结

类型

01背包问题
完全背包问题
多重背包问题(物品个数有限制且不同)
混合背包问题
二维费用的背包问题(重量+体积)
分组背包问题(先分组,组内互斥)
背包问题求方案数。一般是价值,这里是方案数
求背包问题的方案
有依赖的背包问题

零、关于初始化

初始化的细节问题 我们看到的求最优解的背包问题题目中, 事实上有两种不太相同的问法。 有的题 目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满。 一种区别这两种问法的实现方法是在初始化的时候有所不同。
如果是第一种问法, 要求恰好装满背包,那么在初始化时除了 f[0] 为 0 其它 f[1…V] 均设为 - ∞,这样就可以保证最终得到的 f[N] 是一种恰好装满背包的最 优解。
如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 f[0…V] 全部设为 0。
为什么呢?可以这样理解: 初始化的 f 数组事实上就是在没有任何物品可以放入 背包时的合法状态。 如果要求背包恰好装满, 那么此时只有容量为 0 的背包可能 被价值为 0 的 nothing “恰好装满”,其它容量的背包均没有合法的解,属于未 定义的状态,它们的值就都应该是 - ∞了。如果背包并非必须被装满,那么任何 容量的背包都有一个合法解“什么都不装”,这个解的价值为 0,所以初始时状 态的值也就全部为 0 了。-----------------------来自《背包九讲》

一、01背包

01背包问题(物品选不选,物品只能用一次)

二维DP

一个很暴力的方法。
我们按照DP入门博客中讲的步骤进行分析。
最后一步:最后一个物品选或不选。不选就是变成i减小的子问题,选就变成ij同时减小的子问题。dp[i][j]表示选前i个物品重量为j的最大价值。
递推方程:不选第i个物品dp[i][j]=dp[i-1][j],选第i个物品,dp[i][j]=f[i-1][j-v[i]]+w[i];对前两个式子求最值即可。
初始值和边界:f[0][0]=0;要判断重量限制。要限制注意数组规模。
计算顺序:正常小到大
部分代码:

for(int i=1;i<=n;i++){
	for(int j=0;j<=m;j++){
		f[i][j]=f[i-1][j];
		if(j>=v[i]){
			f[i][j]=Math.max(f[i][j],f[i-1][j-v[i]]+w[i]);
		}
	}
}

二维压缩到一维

需要用计算顺序来保证当计算dp[i][j]时,dp[j-k]中存储的都是i-1时的值。
直接修改代码,发现dp[j-k]会被第i次计算覆盖。
所以让j从大到小计算,可以保证dp[j-k]保存着上一轮计算的值不被覆盖。
部分代码:

for(int i=1;i<=n;i++){
	for(int j=m;j>=v[i];j--){
			f[j]=Math.max(f[j],f[j-v[i]]+w[i]);
	}
}

二、完全背包问题

完全背包问题(物品无限选)
我们依然按照DP入门博客中讲的步骤进行分析。并且延续01背包的压缩到一维的想法。

最后一步:第i个物品的最后一个物品装入背包,变为背包体积减小的子问题或者变为物品个数和体积都减小的子问题。压缩到一维之后,这两个子问题的结果共同记录在dp[j-vi]。
递推方程:压缩到一维:dp[j]=max(dp[j],dp[j-vi]+w[i])但是遍历顺序为从小到大**。
初始值和边界:dp[0]=0;
计算顺序:从小到大。在计算第i个物品时,dp[j]中存储的数据不需要为第i-1个物品的情况,因为物品i可以取多个。所以dp[j]的意义变为了前i个物品任意取,体积为j的最大价值。取i-1和取i个物品的情况共同竞争体积为j的dp[j]。
部分代码:

for(int i =0;i<n;i++){
	for(int j=v[i];j<=m;j++){
		dp[j]=Math.max(dp[j],dp[j-vi]+wi);
	}
}

三、多重背包问题

增加一个限制,每个物品最多能使用s[i]件

基本方法

我们仍然按照DP步骤考虑
最后一步:仍然是最后一个物品放入1~k个,变为放入前i-1个物品的子问题。
递推方程:dp[j]=max(dp[j],dp[j-kv[i]+kw[i]);
初始状态:dp[0]=0;
计算顺序:按照i从小到大,j从大到小k从小到大的顺序。压缩到一维。

for(int i=0;i<n;i++){
	for(int j=m;j>=0;j--){
		for(int k=1;k<=s&&k*v[i]<=j;k++){
			dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
		}
	}
}

二进制优化方法

如果物品有x个,就拆成x个物品,放入物品堆。但是正常拆法时间复杂度没有改变。
利用二进制,用log(x)上取整个数的x一定可以表示出x的所有情况。但是!可能会多!对于不能完全取整的,最后一个拆的物品需要单独处理。
例如10:1-2-4-3
代码如下

for(int i=0;i<n;i++){
	for(int k=1;k<=s;k*=2){
		s[i]-=k//物品个数减少。
		goods.add({v[i]*k,w[i]*k});//添加到货物组中
	}
	if(s>0) good.add({v[i]*s,w[i]*s});		
}
//以下同背包问题
for(auto good:good)
	for(int j=m;j>=good.v;j--){
		f[j]=Math.max(f[j],f[j-good.v]+good.w);
	}

究极优化

枚举物品i
枚举体积j归类:模v[i]后分类。类间独立,因为dp[j]只从dp[j-kv]转移。
分别考虑每一类
利用单调队列优化,更新队列的最大值。

混合背包问题

三种物品:只能用一次(s=-1),能用无限次(s=0)和能用s次。
1.先分解,01不变,多重拆解。分为01和完全。
2.如果是01,从大到小枚举体积转移。
3.如果是完全背包,完全背包从小到大枚举体积转移。

二维背包问题

体积和重量限制
一重循环枚举物品
二重循环枚举体积
三重循环枚举重量
因为是01背包,所以从大到小枚举体积和重量

分组背包问题

N组物品,容量V的背包,同一组物品里只能选一个物品。
子问题仍然是前i组物品体积为j最大价值。
一重循环枚举组
二重循环枚举体积从大到小
决策过程:多一个循环。在组内选择哪件物品。(x+1种情况)

猜你喜欢

转载自blog.csdn.net/tianyouououou/article/details/105036081