题意:
从n(35)个数字(有正有负)中取出一个非空子集,使得其和的绝对值最小。
若最小的绝对值有多组,则选择非空子集元素个数最少的。
解析:
折半来枚举。
将35分成两半,第一半状态压缩,然后用map离散化每个sum和num。
然后第二半还是状态压缩完了在map里面找最接近-sum的数。
ps,真是超级喜欢hankcs的c++代码风格。
- - 12s
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <map>
#include <climits>
#include <cassert>
#define LL long long
using namespace std;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double pi = 4 * atan(1.0);
const double ee = exp(1.0);
const int maxn = 30 + 10;
LL a[maxn];
LL Abs(LL x)
{
return x >= 0 ? x : -x;
}
int main()
{
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif // LOCAL
int n;
while (~scanf("%d", &n) && n)
{
for (int i = 0; i < n; i++)
{
scanf("%lld", &a[i]);
}
map<LL, int> mp; // 未取绝对值的sum <-> 元素个数num
pair<LL, int> ans(Abs(a[0]), 1);
int inA = (n >> 1);
int stA = (1 << inA);
int inB = (n - (n >> 1));
int stB = (1 << inB);
for (int i = 0; i < stA; i++)
{
LL sum = 0;
int num = 0;
for (int j = 0; j < inA; j++)
{
if ((i >> j) & 1)
{
sum += a[j];
num++;
}
}
if (num == 0)
continue;
ans = min(ans, make_pair(Abs(sum), num));
map<LL, int>::iterator it = mp.find(sum);
if (it != mp.end())
{
it -> second = min(it -> second, num);
}
else
{
mp[sum] = num;
}
}
for (int i = 0; i < stB; i++)
{
LL sum = 0;
int num = 0;
for (int j = 0; j < inB; j++)
{
if ((i >> j) & 1)
{
sum += a[inA + j];
num++;
}
}
if (num == 0)
continue;
ans = min(ans, make_pair(Abs(sum), num));
map<LL, int>::iterator it = mp.lower_bound(-sum);
if (it != mp.end())
{
ans = min(ans, make_pair(Abs(sum + it -> first), num + it -> second));
}
if (it != mp.begin())
{
--it;
ans = min(ans, make_pair(Abs(sum + it -> first), num + it -> second));
}
}
printf("%lld %d\n", Abs(ans.first), ans.second);
}
return 0;
}