这是最基础的背包问题,特色是:每种物品仅有一件,能够选择放或不放。算法
用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包能够得到的最大价值。则其状态转移方程即是:编程
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
code
这个方程很是重要,基本上全部跟背包相关的问题的方程都是由它衍生出来的。因此有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就能够转化为一个只牵扯前i-1件物品的问题。若是不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];若是放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能得到的最大价值就是f[i-1][v-c[i]]再加上经过放入第i件物品得到的价值w[i]。队列
这个问题很是相似于01背包问题,所不一样的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并不是取或不取两种,而是有取0件、取1件、取2件……等不少种。若是仍然按照解01背包时的思路,令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值。仍然能够按照每种物品不一样的策略写出状态转移方程,像这样:ip
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
class
这跟01背包问题同样有O(VN)个状态须要求解,但求解每一个状态的时间已经不是常数了,求解状态f[i][v]的时间是O(v/c[i]),总的复杂度能够认为是O(V*Σ(V/c[i])),是比较大的。基础
将01背包问题的基本思路加以改进,获得了这样一个清晰的方法。这说明01背包问题的方程的确是很重要,能够推及其它类型的背包问题。但咱们仍是试图改进这个复杂度。循环
这题目和彻底背包问题很相似。基本的方程只需将彻底背包问题的方程略微一改便可,由于对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:方法
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
im
复杂度是O(V*Σn[i])。
考虑到在P01和P02中给出的伪代码只有一处不一样,故若是只有两类物品:一类物品只能取一次,另外一类物品能够取无限次,那么只需在对每一个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环便可,复杂度是O(VN)。伪代码以下:
for i=1..N if 第i件物品属于01背包 for v=V..0 f[v]=max{f[v],f[v-c[i]]+w[i]}; else if 第i件物品属于彻底背包 for v=0..V f[v]=max{f[v],f[v-c[i]]+w[i]};
若是再加上有的物品最多能够取有限次,那么原则上也能够给出O(VN)的解法:遇到多重背包类型的物品用单调队列解便可。但若是不考虑超过NOIP范围的算法的话,用P03中将每一个这类物品分红O(log n[i])个01背包的物品的方法也已经很优了。
固然,更清晰的写法是调用咱们前面给出的三个相关过程。
for i=1..N if 第i件物品属于01背包 ZeroOnePack(c[i],w[i]) else if 第i件物品属于彻底背包 CompletePack(c[i],w[i]) else if 第i件物品属于多重背包 MultiplePack(c[i],w[i],n[i])
在最初写出这三个过程的时候,可能彻底没有想到它们会在这里混合应用。我想这体现了编程中抽象的威力。若是你一直就是以这种“抽象出过程”的方式写每一类背包问题的,也很是清楚它们的实现中细微的不一样,那么在遇到混合三种背包问题的题目时,必定能很快想到上面简洁的解法,对吗?