动态规划之背包之泛化物品
1. 问题描述:
背包问题是一类经典的动态规划问题,它描述了一个背包有一定的容量,而有若干个物品,每个物品有自己的重量和价值,如何从这些物品中选择一部分放入背包,使得背包内物品的总价值最大。背包问题有很多变种,例如0-1背包、完全背包、多重背包等。
泛化物品是一种扩展了背包问题的概念,它指的是每个物品不再是一个单一的实体,而是由若干个部分组成,每个部分有自己的重量和价值,而且可以独立地选择放入或者不放入背包。
例如,
一个物品可以由两个部分组成,第一个部分重量为2,价值为3,第二个部分重量为4,价值为5,那么这个物品就可以看作是一个泛化物品。我们可以选择只放入第一个部分、只放入第二个部分、都放入或者都不放入。
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。
更严格的定义之。在背包容量为V的背包问题中,泛化物品是一个定义域为0..V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。
这个定义有一点点抽象,另一种理解是一个泛化物品就是一个数组h[0..V],给它费用v,可得到价值h[V]。
一个费用为c价值为w的物品,
如果它是01背包中的物品,那么把它看成泛化物品,它就是除了h(c)=w其它函数值都为0的一个函数。
如果它是完全背包中的物品,那么它可以看成这样一个函数,仅当v被c整除时有h(v)=v/cw,其它函数值均为0。
如果它是多重背包中重复次数最多为n的物品,那么它对应的泛化物品的函数有h(v)=v/cw仅当v被c整除且v/c<=n,其它情况函数值均为0。
一个物品组可以看作一个泛化物品h。对于一个0..V中的v,若物品组中不存在费用为v的的物品,则h(v)=0,否则h(v)为所有费用为v的物品的最大价值。P07中每个主件及其附件集合等价于一个物品组,自然也可看作一个泛化物品。
2. 泛化物品的和
如果面对两个泛化物品h和l,要用给定的费用从这两个泛化物品中得到最大的价值,怎么求呢?事实上,对于一个给定的费用v,只需枚举将这个费用如何分配给两个泛化物品就可以了。同样的,对于0..V的每一个整数v,可以求得费用v分配到h和l中的最大价值f(v)。也即
f(v)=max{h(k)+l(v-k)|0<=k<=v}
可以看到,f也是一个由泛化物品h和l决定的定义域为0..V的函数,也就是说,f是一个由泛化物品h和l决定的泛化物品。
由此可以定义泛化物品的和:h、l都是泛化物品,若泛化物品f满足以上关系式,则称f是h与l的和。这个运算的时间复杂度取决于背包的容量,是O(V^2)。
泛化物品的定义表明:在一个背包问题中,若将两个泛化物品代以它们的和,不影响问题的答案。事实上,对于其中的物品都是泛化物品的背包问题,求它的答案的过程也就是求所有这些泛化物品之和的过程。设此和为s,则答案就是s[0..V]中的最大值。
3. 求解过程
泛化物品的背包问题可以用动态规划来求解,我们可以定义一个状态数组dp[i][j]表示前i个物品在容量为j的背包中能够获得的最大价值。那么我们可以得到如下的状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w1]+v1, dp[i-1][j-w2]+v2, dp[i-1][j-w1-w2]+v1+v2)
其中w1和w2分别表示第i个物品的第一个部分和第二个部分的重量,
v1和v2分别表示第i个物品的第一个部分和第二个部分的价值。
这个方程的含义是:
对于第i个物品,我们有四种选择:不放入任何部分、只放入第一个部分、只放入第二个部分、都放入。我们要在这四种选择中选取一个使得总价值最大的。
4. 具体实现
public class GeneralizedItemKnapsack {
public static int knapsack(int[] w1, int[] w2, int[] v1, int[] v2, int m) {
// w1 and w2 are the weights of the first and second parts of each item
// v1 and v2 are the values of the first and second parts of each item
// m is the capacity of the knapsack
int n = w1.length; // the number of items
int[][] dp = new int[n + 1][m + 1]; // the state array
// initialize the state array
for (int j = 0; j <= m; j++) {
dp[0][j] = 0;
}
// update the state array according to the state transition equation
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
dp[i][j] = dp[i - 1][j]; // do not put any part of the ith item
if (j >= w1[i - 1]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - w1[i - 1]] + v1[i - 1]); // only put the first part of the ith item
}
if (j >= w2[i - 1]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - w2[i - 1]] + v2[i - 1]); // only put the second part of the ith item
}
if (j >= w1[i - 1] + w2[i - 1]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - w1[i - 1] - w2[i - 1]] + v1[i - 1] + v2[i - 1]); // put both parts of the ith item
}
}
}
return dp[n][m]; // return the maximum value
}
public static void main(String[] args) {
// test case
int[] w1 = {2, 3, 4, 5};
int[] w2 = {4, 5, 6, 7};
int[] v1 = {3, 4, 5, 6};
int[] v2 = {5, 6, 7, 8};
int m = 10;
System.out.println(knapsack(w1, w2, v1, v2, m)); // output: 15
}
}
5. 泛化物品背包问题的分析
泛化物品背包问题的动态规划算法的时间复杂度是O(nm),其中n是物品的数量,m是背包的容量。这是因为我们需要遍历所有的物品和所有的容量,对于每个状态,我们需要做四次比较,每次比较的时间是O(1)。因此,总的时间复杂度是O(4nm) = O(nm)。
泛化物品背包问题的动态规划算法的空间复杂度也是O(nm),这是因为我们需要一个二维数组来存储所有的状态。如果我们只关心最终的最大价值,而不关心具体的选择方案,那么我们可以用一维数组来优化空间复杂度,将其降低到O(m)。具体的做法是,我们只保留上一行的状态,然后用滚动数组的方式更新当前行的状态。这样,我们就不需要额外的空间来存储整个二维数组了。
泛化物品背包问题是一个比较有趣的问题,它可以用来模拟一些实际场景中的优化问题。例如,如果我们有一些可拆分的物品,每个物品可以分成两个部分,每个部分有自己的重量和价值,而且可以独立地选择放入或者不放入背包。那么我们就可以用泛化物品背包问题来求解最优的选择方案。例如,如果我们有一个电脑,它可以分成主机和显示器两个部分,主机重量为6,价值为8,显示器重量为4,价值为5,那么我们就可以把它看作是一个泛化物品。如果我们有一个容量为10的背包,那么我们应该选择把主机和显示器都放入背包,这样可以获得最大的价值13。
6. 背包问题的泛化物品
一个背包问题中,可能会给出很多条件,包括每种物品的费用、价值等属性,物品之间的分组、依赖等关系等。但肯定能将问题对应于某个泛化物品。也就是说,给定了所有条件以后,就可以对每个非负整数v求得:若背包容量为v,将物品装入背包可得到的最大价值是多少,这可以认为是定义在非负整数集上的一件泛化物品。这个泛化物品——或者说问题所对应的一个定义域为非负整数的函数——包含了关于问题本身的高度浓缩的信息。一般而言,求得这个泛化物品的一个子域(例如0..V)的值之后,就可以根据这个函数的取值得到背包问题的最终答案。
综上所述,一般而言,求解背包问题,即求解这个问题所对应的一个函数,即该问题的泛化物品。而求解某个泛化物品的一种方法就是将它表示为若干泛化物品的和然后求之。
7. 小结
本文介绍了泛化物品背包问题的概念和动态规划算法,并给出了JAVA语言的实现和分析。泛化物品背包问题是一种扩展了背包问题的概念,它可以用来求解一些实际场景中的优化问题。动态规划算法可以有效地求解泛化物品背包问题,它具有较低的时间复杂度和空间复杂度。
本讲可以说都是我自己的原创思想。具体来说,是我在学习函数式编程的 Scheme 语言时,用函数编程的眼光审视各类背包问题得出的理论。这一讲真的很抽象,也许在“模型的抽象程度”这一方面已经超出了NOIP的要求,所以暂且看不懂也没关系。相信随着你的OI之路逐渐延伸,有一天你会理解的。
我想说:“思考”是一个OIer最重要的品质。简单的问题,深入思考以后,也能发现更多。
8.参考
- DP复习——泛化物品背包_泛化物品 例题_千杯湖底沙.的博客-CSDN博客 这篇博客介绍了泛化物品的概念、定义、和运算,以及如何将背包问题转化为泛化物品问题,并给出了一个例题和代码。
- 动态规划 —— 背包问题 P08 —— 泛化物品背包 - CSDN博客 这篇博客也介绍了泛化物品的概念、定义、和运算,以及如何求解泛化物品问题,并给出了一个例题和代码。
- 泛化物品 - MBA智库百科 这篇百科简要地介绍了泛化物品的概念和定义。
- 泛化物品_想去的远方的博客-CSDN博客 这篇博客也简要地介绍了泛化物品的概念和定义。