问题描述:
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
问题分析:
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第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]}。复杂度是O(V*Σn[i])。
一维法:
public static int MultiplePack1(int W, int[] w, int[] v, int[] num) { int n = w.length - 1;// 第一个值,不算 int[] f = new int[W + 1]; for (int i = 1; i <= n; i++) for (int j = W; j >= w[i]; j--) for (int k = 0; k <= num[i] && j - k * w[i] >= 0; k++) { f[j] = Math.max(f[j], f[j - k * w[i]] + k * v[i]); } return f[W]; // 最优解 }
二进制优化做法:
首先,可以把它转化为一个01背包的问题。每个物品有s件,我们可以把它差分成s份,每份物品当做不同的个体,即只能选一次,这就转化为了01背包物品,但是这样的话,复杂度还是很大。
继续优化,一个物品的数量是s的话,只要把s拆分成一些数字,使它们能够表示出1-s中任意一个数字,就可以,没必要把它拆成s个1。
那么这样的数字最少需要多少个呢?最少需要log(s)个,向上取整。比如7,它最少需要3个数字来表示:
即 1(2^0=1 ), 2(2^1=2), 4(2^2=4)。原因:每个数字有2种可能选或不选,那么可以表示的不同数字个数就是 2 * 2 * 2 = 8。但是还需要注意一个问题,就是有些数字可能能够表示出来一些大于s的数字,但是这件物品最多只有s件,那么就需要特殊处理一下最后一个数。
比如10,若用1,2, 4, 8表示,可能会表示出来大于10的数字,例如:4+8=12。那么如果最后一个数字加上前面数的总和会大于s,就将它替换为剩下的物品个数,即将8替换为3,这时正好能表示出1-s所有的数,-> 1, 2,4可以表示7以内的所有数,这些数加上3就可以表示10以内的所有数啦。
(参考背包九讲)
一维法(二进制优化):
public static int MultiplePack2(int W, int[] w, int[] v, int[] num) { int n = w.length; int[] f = new int[W + 1]; int[] vv = new int[n*W]; int[] ww = new int[n*W]; // 复制w和v for(int i=0;i<n;i++) { vv[i] = v[i]; ww[i] = w[i]; } k = n; for(int i=0;i<n;i++) { s = num[i] for(int i=1;i<=s;i*=2) //二进制优化 vv[k]=v[i]*i,ww[k++]=w[i]*i,s-=i; if(s>0) vv[k]=v[i]*s,ww[k++]=w[i]*s; } for (int i = 1; i <= k; i++) //01背包 for (int j = W; j >= ww[i]; j--) f[j] = Math.max(f[j], f[j - ww[i]] + vv[i]); return f[W]; // 最优解 }
浙公网安备 33010602011771号