集合划分问题

问题:

给定一个数组,每个元素范围是0~K(K < 整数最大值2^32),将该数组分成两部分,使得 |S1- S2|最小,其中S1和S2分别是数组两部分的元素之和。

分析:

问题本质是01背包问题。

(1)每个元素值既是价值也是重量。

(2)背包承重上限为所有元素总和的一半;设为mid = sum/2。

(3)问题转化为寻找[0,mid]范围内的最大价值sum1。

(4)最后结果:|sum-2*sum1|,当sum1==mid时,获得最优解0.

(5)为了数据溢出错误使用double类型记录值,最后使用格式化输出整型结果。

代码:

 

 1 import java.util.*;
 2 
 3 /**
 4  * 背包问题
 5  * 假设背包承重上限为sum/2
 6  * 找出价值最接近sum/2的物品集合
 7  */
 8 public class Main{
 9     public static void main(String args[]){
10             Scanner in = new Scanner(System.in);
11             int n = in.nextInt();
12             double[] ar = new double[n];    //记录数据
13 
14             for(int i=0;i<n;i++){
15                 ar[i] = in.nextDouble();
16             }
17             double sum = Arrays.stream(ar).sum();
18             double midl = sum/2;
19             int maxw = (int)Math.ceil(midl);
20             double[] dp = new double[maxw+1];
21         //开始处理背包问题:提前结束条件->sumx = midel;
22             //初始化
23             for(int i=0;i<=maxw;i++){
24                 dp[i] = ar[0]<i?0:ar[0];
25             }
26             boolean flag=false;
27             double sum1 = ar[0];
28             for(int i=1;i<n && !flag;i++){
29                 //当元素大于midel,自动过滤
30                 for(int j=maxw;j>=ar[i];j--){
31                     int index = (int)(j-ar[i]);
32                     double tsum1 = dp[index]+ar[i]; //选择该元素
33                     if(tsum1==midl){
34                         flag=true;
35                         break;
36                     }
37                     if(tsum1<midl && tsum1>dp[j]){
38                         sum1 = tsum1;
39                         dp[j] = tsum1;
40                     }
41                 }
42             }
43        if(flag){
44            System.out.println(0);
45        }else{
46            System.out.printf("%.0f",Math.abs(sum-2*sum1));
47        }
48     }
49 }
View Code

 

posted @ 2020-07-14 22:40  Yrc的楚门的世界  阅读(313)  评论(0编辑  收藏  举报