[2017浙工大之江学院决赛 E] qwb和李主席(折半枚举,二分)

题目链接:http://115.231.222.240:8081/JudgeOnline/problem.php?cid=1005&pid=4

题意:把一个数组拆成两部分,使得两个集合分别的和的差的绝对值最小。

做过类似的,用01背包,求sum/2容量下的最大价值,这样可以拆成两个集合,并且符合题意。

但是这题浮点数,而且物品价值1e9,不能背包了。

n<=36,也不能直接枚举。

可以把n个数拆成两部分,先枚举一部分的组合情况,把和求出来,再枚举另一部分,枚举到一个和x后在第一部分的和里二分地枚举一个和y,使得x+y最接近sum/2。然后每次更新两部分的和A=x+y, B=sum-A,更新ret=min(abs(A-B))即可。

 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 const int maxn = 66;
 5 int n, na, nb;
 6 double v[maxn], a[maxn], b[maxn];
 7 double s[1<<20], sum;
 8 
 9 double gao(double val) {
10     int lo = 1, hi = (1 << nb), ret = -1;
11     while(lo <= hi) {
12         int mid = (lo + hi) >> 1;
13         if((s[mid] + val) * 2.0 >= sum) {
14             ret = mid;
15             hi = mid - 1;
16         }
17         else lo = mid + 1;
18     }
19     double A = s[ret] + val , B = sum - A;
20     return abs(A - B);
21 }
22 
23 int main() {
24     // freopen("in", "r", stdin);
25     while(~scanf("%d",&n)) {
26         na = nb = 0;
27         sum = .0;
28         memset(s, 0, sizeof(s));
29         for(int i = 1; i <= n; i++) {
30             scanf("%lf", &v[i]);
31             sum += v[i];
32             if(i <= n / 2) a[na++] = v[i];
33             else b[nb++] = v[i];
34         }
35         for(int i = 0; i < (1 << na); i++) {
36             for(int j = 0; j < na; j++) {
37                 if(i & (1 << j)) s[i+1] += a[j];
38             }
39         }
40         sort(s+1, s+(1<<na)+1);
41         double ret = 1e18;
42         for(int i = 0; i < (1 << nb); i++) {
43             double tmp = .0;
44             for(int j = 0; j < nb; j++) {
45                 if(i & (1 << j)) tmp += b[j];
46             }
47             // double A = tmp + *lower_bound(s+1, s+(1<<na)+1, sum/2.0-tmp);
48             // double B = sum - A;
49             ret = min(ret, gao(tmp));
50         }
51         printf("%.2f\n", ret);
52     }
53     return 0;
54 }

 

posted @ 2017-06-03 12:20  Kirai  阅读(177)  评论(0编辑  收藏  举报