题目链接: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 ", ret); 52 } 53 return 0; 54 }