DP中的背包问题

小学期学的算法和做OJ题的各种经验总结


01背包问题

不同的物品每种有一件

  1. 不能放入:
    没有足够的空间给第i件物品,即j<wi,那么空间没有变化,价值也没有变化: d i j = d i − 1 ,   j d_{ij} = d_{i-1,\space j} dij=di1, j
  2. 能放入但不想放入:和不能放入其实是一样的,动机不同但结果相同。同样空间没有变化,价值没有变化: d i j = d i − 1 ,   j d_{ij} = d_{i-1,\space j} dij=di1, j
  3. 想放入:必须要给这件物品留足够的空间才能放入,只有在di-1,j-w[i] 的基础上才能保证有空间放入。
    放入后空间变化了,价值也变化了: d i j = d i − 1 ,   j − w i + v i d_{ij} = d_{i-1,\space j-w_i} + v_i dij=di1, jwi+vi
  4. 但是想不想放入呢?(2,3中)如果放入之后结果更好那我肯定放,如果放了还不如不放呢那我肯定不想放。怎么确定哪个选项更好呢,就要比较2和3等式右边的值,哪个大哪个就更好。并且用dij记录下来。
  5. 综上,最终结果:
    d i j = { d i − 1 , j , j &lt; w i m a x ( d i − 1 , j , d i − 1 ,   j − w i + v i ) , j ≥ w i d_{ij}= \begin{cases} d_{i-1 ,j}, &amp;j&lt;w_i\\ max(d_{i-1 ,j}, d_{i-1,\space j-w_i} + v_i), &amp;j \ge w_i \end{cases} dij={di1,j,max(di1,j,di1, jwi+vi),j<wijwi
  • 优化

    • d[i][…]只与d[i-1][…]有关,所以可以滚动数组节省空间
    • 给一维数组开W(总重量)的空间,外层i从0~n循环,内层j从W到0循环(一定要倒着,原因如下:
    1. 假设i-1层数据已经存好了,现在处理第i层。
    2. 能放入的时候, d j = m a x ( d j ,    d j − w i + v i ) d_j = max(d_j, \space \space d_{j-w_i}+v_i) dj=max(dj,  djwi+vi)
    3. 其中,dj = dj 相当于数据没变, d j − w i d_{j-w_i} djwi是读取比j 小的位置的内容,我们让j倒着循环,每个位置都是以比它小的位置做参考,不会丢失当前需要的信息。而覆盖的位置也是对于这一层i后面更小的j不会参考的信息,属于旧数据,可以覆盖掉。
    4. 最终结果就是,只用新数据覆盖数据
    5. 因此,最终公式就是,不能放入的时候,dj = dj 不变;能放入的时候, d j = m a x ( d j ,    d j − w i + v i ) d_j = max(d_j,\space \space d_{j-w_i}+v_i) dj=max(dj,  djwi+vi)
      可以简化成:
      d j = { d j , j &lt; w i m a x ( d j , d j − w i + v i ) , j ≥ w i d_j= \begin{cases} d_j, &amp; &amp;j&lt;w_i \\ max(d_j,&amp; d_{j-w_i}+v_i),&amp; j \ge w_i \end {cases} dj={dj,max(dj,djwi+vi),j<wijwi
      注意遍历区域和顺序
    for(j=W; j>=w[i]; j--) 
    	if (d[j-w[i]]+v[i] > d[j]) 
    		d[j] = d[j-w[i]]+v[i];
    
  • 是否恰好装满

    • 如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该 将F[0…V ]全部设为0。

    • 如果要求恰好装满背包,那么在初始化时除了F[0]为0,其 它F[1…V ]均设为−∞,这样就可以保证最终得到的F[V]是一种恰好装满背包的最优解。

    • 可以这样理解:初始化的F数组事实上就是在没有任何物品可以放入背包时的合法状态。

      如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

      如果要求背包恰好装满,那么此时只有容量为0的背包可以在什么也不装且价值为0的情况下被“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,应该被赋值为-∞了。那么新的物品进行比较时只有恰好能装这个物品的空间的数组才是合法的,有多出来的空间的是非法的,就不会参与比较。

代码

const int MAXN = 105;
int d[MAXN][MAXN];
int f[MAXN];//滚动数组
int W, v[MAXN], w[MAXN], n;

void normalDP(){
	for(int i=1; i<=n; i++){
		for(int j=0; j<=W; j++){
			if(j<w[i]) d[i][j] = d[i-1][j];
			else d[i][j] = max(d[i-1][j], d[i-1][j-w[i]] + v[i]);
		}
	}
	cout<<d[n][W];
}

一维滚动数组实现

//滚动数组
void betterDP(){
	for(int i=1; i<=n; i++){
		for(int j=W; j>=w[i]; j--){//j小于w[i]放不下,一定不更新
			if ((f[j-w[i]] + v[i] > f[j])) f[j] = f[j-w[i]] + v[i];
		}
	}
	cout<<f[W];
}

完全背包问题

不同的物品每种有无限件

  • 思路与01背包类似:
  • 用dij表示前i个物品剩余空间为j时的最大总价值,对于第i个物品:
  1. 不能放入:
    没有足够的空间给第i件物品,即j<wi,那么空间没有变化,价值也没有变化: d i j = d i − 1 ,   j d_{ij} = d_{i-1,\space j} dij=di1, j

  2. 能放入但不想放入:和不能放入其实是一样的,动机不同但结果相同。同样空间没有变化,价值没有变化: d i j = d i − 1 ,   j d_{ij} = d_{i-1,\space j} dij=di1, j

  3. 想放入:
    要想放入k件,应该至少预留k个空间。(划掉) d i j = max ⁡ { d i − 1 , j − k ∗ w i + k ∗ v i , k ≥ 1 } d_{ij} =\max \{ d_{i-1,j-k*w_i}+k*v_i ,k≥1\} dij=max{di1,jkwi+kvi,k1}(划掉)

    假设dij有k-1件i物品,dij至少应该留有一件(第k件)第i种物品的空间,所以要预留一个wi的空间来存放第k件第i种物品: d i j = d i ,   j − w i + v i d_{ij} = d_{i,\space j-w_i} + v_i dij=di, jwi+vi
    (我们要确保dij至少有一件第i件物品,所以要预留wi的空间来存放一件第i种物品。)

  4. 比较2,3中等式右边的值,选大的那个

  5. 综上,最终结果:
    d i j = { d i − 1 , j , j &lt; w i m a x ( d i − 1 , j , d i , j − w i + v i ) , j ≥ w i d_{ij}= \begin{cases} d_{i-1 ,j}, &amp;j&lt;w_i\\ max(d_{i-1 ,j}, d_{i,j-w_i} + v_i), &amp;j \ge w_i \end{cases} dij={di1,j,max(di1,j,di,jwi+vi),j<wijwi

完全背包

  • 一维数组实现:每一个新的dij是建立在i不变j之前的dix的基础上的(注意遍历顺序)
    d j = { d j , j &lt; w i m a x ( d j , d j − w i + v i ) , j ≥ w i d_j= \begin{cases} d_j, &amp; j&lt;w_i \\ max(d_j,&amp;d_{j-w_i}+v_i),&amp; j \ge w_i \end {cases} dj={dj,max(dj,j<widjwi+vi),jwi
void completeBagDP(){
	for(int i=1; i<=n; i++){
		for(int j=w[i]; j<=W; j++){//j小于w[i]放不下,一定不更新
			d[j] = max(d[j],  d[j-w[i]] + v[i]);
			/*if(d[j-w[i]] + v[i] > d[j])
				d[j] = d[j-w[i]] + v[i];*/
		}
	}
	cout<<d[W];
}

区别0-1背包和完全背包

  • 从二维数组上,也就是状态转移方程上区别,就差在放第i种物品时,完全背包在选择放这个物品时,最优解是 d i ,   j − w i + v i d_{i,\space j-w_i} + v_i di, jwi+vi,即画表格中同行的那一个,而0-1背包比较的是 d i − 1 ,   j − w i + v i d_{i-1,\space j-w_i} + v_i di1, jwi+vi,是表格中上一行的那一个。

  • 从一维数组上区别0-1背包和完全背包差别就在循环顺序上,0-1背包必须逆序,因为这样保证了不会重复选择已经选择的物品,而完全背包是正序,顺序会覆盖以前的状态,取的信息也是建立在新数据的基础上,符合存在选择多次的情况。

  • 状态转移方程都为 d j = m a x ( d j ,   d j − w i + v i ) ,   j ≥ w i d_j=max(d_j,\space d_{j-w_i}+v_i),\space j \ge w_i dj=max(dj, djwi+vi), jwi


多重背包

第i种物品有ni件。

  • 转化为01背包,把相同物品看成不同物品即可。
  • 比如,有2件价值为5,重量为2的同一物品,我们就可以分为物品a和物品b,a和b的价值都为5,重量都为2,但我们把它们视作不同的物品。
  • 对于某一件物品,放入Ci次
for (int i = 0; i < prices.size(); ++i)
	for (int k = 1; k <= amounts[i]; ++k)
		for (int j = n; j >= prices[i]; --j)
			dp[j] = max(dp[j - prices[i]] + weight[i], dp[j]);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值