題目連結: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;}