题目链接:http://poj.org/problem?id=3977
题意:给你n个数,求出这n个数的一个非空子集,使子集中的数加和的绝对值最小,在此基础上子集中元素的个数应最小。
思路:n为35,直接枚举的复杂度肯定是容不下的。但我们可以把这些数分为两半,算出前一半元素所有子集对应的权和及个数,对后一半元素的每个子集,设其元素之和为sum,用二分查找的方法在前一半中找元素和最接近-sum的子集即可。
#include<map>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> P;
map<ll, int> mp;
ll a[50];
ll Abs(ll x) {
return x < 0 ? -x : x;
}
bool input(int& n) {
cin >> n;
if(n == 0) return false;
for(int i = 0; i < n; i++) cin >> a[i];
mp.clear();
return true;
}
void solve(int n) {
P ans = P(Abs(a[0]), 1);
map<ll, int> :: iterator it;
int mid = n >> 1;
//枚举前半部分
for(int i = 0; i < 1<<mid; i++) {
ll sum = 0; int num = 0;
for(int j = 0; j < mid; j++) if(i >> j & 1) {
sum += a[j]; ++num;
}
if(num == 0) continue;
ans = min(ans, P(Abs(sum), num)); //全在前半段
// 插入map
it = mp.find(sum);
if(it != mp.end()) it->second = min(it->second, num);
else mp[sum] = num;
}
//枚举后半部分
for(int i = 0; i < 1<<(n-mid); i++) {
ll sum = 0; int num = 0;
for(int j = 0; j < n-mid; j++) if(i >> j & 1) {
sum += a[j+mid]; ++num;
}
if(num == 0) continue;
ans = min(ans, P(Abs(sum), num)); //全在后半段
//在map中寻找答案
it = mp.lower_bound(-sum); //使这两半的和的绝对值最小,前半段要尽量接近-sum
if(it != mp.end()) ans = min(ans, P(Abs(sum + it->first), it->second + num));
if(it != mp.begin()) --it, ans = min(ans, P(Abs(sum + it->first), it->second + num));
}
//输出答案
cout << ans.first << ' ' << ans.second << endl;
}
int main() {
int n;
while(input(n)) {
solve(n);
}
return 0;
}