1. 程式人生 > >[2017浙工大之江學院決賽 E] qwb和李主席(折半枚舉,二分)

[2017浙工大之江學院決賽 E] qwb和李主席(折半枚舉,二分)

題目 code http int 學院 cnblogs return tdi 更新

題目鏈接: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 }

[2017浙工大之江學院決賽 E] qwb和李主席(折半枚舉,二分)