背包
作者:互联网
背包是线性DP中一类重要而特殊的模型。
没有骚话水了下面就直入主题,看一下DP中的“常客”————背包问题。
以01背包的模板题为例。
有N件物品和一个容量为V的背包。第i件物品的体积是Ci,得到的价值是Wi。求解将那些物品装入背包可使装入背包的价值总和最大。
题目很简短(当然对于各个大犇也很简单但是我想水),首先还是看一下阶段如何来定义,这里我们采用一个二元数组dp[i][j]来表示从前i件物品中挑选了一些物品,其所用体积为j时,物品的最大价值和。
这样我们就不需要理会前面已完成的部分,而只需考虑如何用其来更新就行。
假设我们已经知道有关dp[i][j]的前面的阶段,我们该如何得出dp[i][j]呢?
- 如果不取第i件物品 dp[i][j]=dp[i-1][j]
- 如果取第i件物品 dp[i][j]=dp[i-1][j-Ci](当然需要满足j>=Ci)
for(int i=1;i<=n;i++){
dp[i][j]=dp[i-1][j];
for(int j=c[i];j<=v;j++)
dp[i][j]=max(dp[i][j],dp[i-1][j-c[i]]+w[i]);
}
但是作为一名有着三十年代码经验的oler为了节省空间,我们还可以这样……
for(int i=1;i<=n;i++)
for(int j=v;j>=c[i];j--)
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
为什么可以省掉一维呢?
仔细查看,我们发现dp[i][j]的值可以看作是“一行一行地传递下来的”,且每一行只跟上一行有关,那么我们就可以让其“一行一行地覆盖”,从而只需一维来节省空间。
但为什么要让j倒着来呢?很简单,dp[j]只可能被dp[x(x<j)]更新,只要我们倒着来,那么为遍历过的就会保持着“上一行”的状态,不然,有可能dp[x(x<j)]已经被更新(即已经是取过第i件物品的状态),再用其更新就会违背01背包问题的定义。
值得一提的是,完全背包问题的模板就是把01背包每件只能取一件换成可无限取。
那么,此时只需这样……
for(int i=1;i<=n;i++)
for(int j=c[i];j<=v;j++)
dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
对,就是把内层循环改回正的就行了!
But why?(好吧,大家都知道,这样问显得鄙人很蠢……)
因为正着来,dp[x(x<=j)]已经被遍历过,说明已经是考虑过是否取该件物品的,也就是可能取过很多次,符合完全背包的定义。
除了01和完全外,背包还有多重与分组。
多重背包,其实就是01背包的再魔改:将取一件改为取限定数件。
处理也很简单,就是将其件数进行拆分。那么,怎么拆?
且看我抄书分析————
我们找到一个数p,使得20+21+22+……+2p<=s(s为数量),再令x=s-20+21+22+……+2p,那么,我们就能用20到2p和x这些数构建出小于等于s的任意数。
为什么?因为x显然小于2(p+1),而20到2p可以构造出1到2(p+1)-1中的任意数,此时加上x,就可将任意构造的范围扩大到1到s。
所以只要按照这种方法,把该件物品分成(p+2)件物品(此时物品体积为20*C~i~到2pCi与xCi,且每件只能取一件),就能将问题变为01背包,再进行解决。
那么,代码就不需要贴了。其实是没写
最后,再水一下介绍一下多重背包。
额,其实吧,这货还是01背包魔改……只要把i件物品改为有i组物品,每组只取一件。
那么模板也是小改一下就行。
for(int i=1;i<=n;i++)
for(int j=v;j>=0;j--)
for(int k=1;k<=s[i];k++)
if(j>=c[i][k])
dp[j]=max(dp[j],dp[j-c[i][k]]+w[i][k]);
注意k只能在最内层,因为要保证每组只能选一件,放在里面并配合j的倒序循环,只会被“上一层”更新,从而实现每组只取一件的目的。
当然这些只是最简单的模板,一般题目都会在其基础上进行“修饰”或“难度加倍”,那就应对其内在的细节把握得更清晰。
最后,终于水完了!!!
标签:件物品,20,01,int,背包,dp 来源: https://www.cnblogs.com/hh--/p/16583693.html