Subset
Time Limit: 30000MS | Memory Limit: 65536K | |
Total Submissions: 3161 | Accepted: 564 |
Description
Given a list of N integers with absolute values no larger than 1015, find a non empty subset of these numbers which minimizes the absolute value of the sum of its elements. In case there are multiple subsets, choose the one with fewer elements.
Input
The input contains multiple data sets, the first line of each data set contains N <= 35, the number of elements, the next line contains N numbers no larger than 1015 in absolute value and separated by a single space. The input is terminated with N = 0
Output
For each data set in the input print two integers, the minimum absolute sum and the number of elements in the optimal subset.
Sample Input
1 10 3 20 100 -100 0
Sample Output
10 1 0 2
Source
解析:折半枚举。N个元素的集合,子集有2N个,除去空集,有2N-1个子集。而N可达35,235-1这个数就很大了,即使给了30000ms,直接枚举也会超时,可以考虑折半枚举。把集合分为两部分,这样两个小集合元素规模至多为18,枚举量为218,在可以承受的范围内。这样就得到了两个可枚举的集合A和B,最终的结果来源于这三种情况:子集的元素只取自于A、子集的元素只取自于B、子集的元素取自A和B。对于子集的元素只取自于A、子集的元素只取自于B这两种情况,我们在枚举的时候不断更新就可以了。对于子集的元素取自A和B这种情况,我们在枚举B(假设得到的子集中所有元素的和为sum)时,最佳情况为在A中找到-sum,这样我们在二分查找时,在-sum附近进行更新即可。另外,POJ暂不支持64位的abs,自己写一个吧^_^
#include <cstdio> #include <map> #define ll long long using namespace std; int n; ll a[40]; ll ll_abs(ll x) { return x >= 0 ? x : -x; } void solve() { map<ll, int> mp; map<ll, int>::iterator it; pair<ll, int> res(ll_abs(a[0]), 1); //初始化结果为第一个元素 for(int i = 1; i < 1<<(n/2); ++i){ //枚举区间为[1, 2^n),当i为0时,子集为空 ll sum = 0; int num = 0; for(int j = 0; j < n/2; ++j){ //按位枚举 if((i>>j)&1){ sum += a[j]; ++num; } } res = min(res, make_pair(ll_abs(sum), num)); //子集的元素只取自于A it = mp.find(sum); if(it != mp.end()) it->second = min(it->second, num); else mp[sum] = num; } for(int i = 1; i < 1<<(n-n/2); ++i){ ll sum = 0; int num = 0; for(int j = 0; j < n-n/2; ++j){ if((i>>j)&1){ sum += a[n/2+j]; ++num; } } res = min(res, make_pair(ll_abs(sum), num)); //子集的元素只取自于B it = mp.lower_bound(-sum); //查找与-sum最相近的值 if(it != mp.end()) //可能在该位置 res = min(res, make_pair(ll_abs(it->first+sum), it->second+num)); if(it != mp.begin()){ //可能在该位置的前一个位置 --it; res = min(res, make_pair(ll_abs(it->first+sum), it->second+num)); } } printf("%I64d %d ", res.first, res.second); } int main() { while(scanf("%d", &n), n){ for(int i = 0; i < n; ++i) scanf("%I64d", &a[i]); solve(); } return 0; }