多重背包的二进制优化
现在有价值分别为1到6的六种物品,数量分别是n1,n2,n3,n4,n5,n6,问是否可以把这些物品分成价值相等的两份?
输入格式
输入中的每一行描述了要分割的一个物品集合。这些物品由六个非负整数n1,n2,…,n6组成,其中ni是价值i的物品数。最大物品总数为20000。 输入文件的最后一行是“0 0 0 0 0 0”;不要处理这一行。
输出格式
1 0 1 2 0 0
1 0 0 0 1 1
0 0 0 0 0 0
对应结果
NO
YES
思路
多重背包暴力解法时间复杂度为O(N * V * S)
当物品数量较大时,一个一个的选择浪费了太多的时间,必然超时。
我们采用二进制优化的思想,将同种类的物体按照二进制进行分组,每一组合并成一个新的价值和重量的物品,第 i 种物品按照以下数量分组:1,2,4,8,16...... 2^k......512
(
2
0
,
2
1
,
2
2
,
2
3
,
.
.
.
2
k
,
.
.
.
2
9
2^0, 2^1, 2^2, 2^3,...2^k,...2^9
20,21,22,23,...2k,...29)此时 (1~1023) 中的每一个数都可以通过以上分组的组合表示出来(二进制)。
例子:
19 = ( 10011 ) 2 = 2 0 + 2 1 + 2 4 19 = (10011)_2 = 2^0 + 2^1 + 2^4 19=(10011)2=20+21+24
当有剩余数量无法被2的幂次方表示时:
例子:第 i 种物品有200个:
可以被2的幂次方表示的有:1,2,4,8...64
1,2,4,8...64
,可以使用2的幂次方表示出来,可以表示出来 (1~127) 中的所有数,这是还没使用完200这个数,还有剩余的个数为200-127 = 73个,将其单独成新的一组,就可以表示出 (1~200) 中的任意数。
最终的分组情况为:1,2,4,8...64,73
而每一组合并成了一个新的物品,新的物品的价值为原来物品的价值 * 个数
,新物品的重量为原来物品的重量 * 个数
,以上述200个物品为例,假设该物品价值为2,重量为3,则新物品的价值分别是:2,4,8,16...128,146
,新物品的重量分别是:3,6,12,24,192,219
。
所以,按照二进制的形式分组选择,加快选取速度。
并且对于该题,物品的价值和体积相等。
代码实现
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true) {
//价值数组
int[] v = new int[10000];
//记录价值数组下标
int index = 0;
//所有物品价值总和
int sum = 0;
for(int i = 0; i < 6; i++) {
int num = sc.nextInt();
sum += num * (i + 1);
//二进制分组
for(int k = 1; k <= num; k *= 2, num -= k, index++) {
v[index] = k * (i + 1);
}
//无法用2的幂次方表示的单独再分一组
if(num > 0) {
v[index++] = num * (i + 1);
}
}
if (sum == 0) {
break;
}
sc.nextLine();
//总和是奇数的一定不能分成相等的两份
if(sum % 2 != 0) {
System.out.println("NO");
continue;
}
int bagWeigh = sum / 2;
int[] dp = new int[bagWeigh + 1];
for(int i = 0; i < index; i++) {
for(int j = bagWeigh; j >= v[i]; j--) {
dp[j] = Math.max(dp[j - v[i]] + v[i], dp[j]);
}
}
if(dp[bagWeigh] == bagWeigh) {
System.out.println("YES");
}else {
System.out.println("NO");
}
}
sc.close();
}
}