Subset(折半枚举+二分查找)
题意:
给定个数,要求找到一个子集,使得子集内所有数绝对值之和最小,当有不同子集都最小时,输入子集大小最小者。输入最小的和和子集的大小(
)
题解:
观察到,如果对这
个数都进行枚举,当
较大时就会超时,但是考虑到折半枚举,分成
和
两部分,最高是
次,是可以接受的。因此可以对前
算出每种情况的绝对值之和,同时记录每种情况的和和数量。在枚举后
时,只需要同前
一样算出每种情况和的同时,在算出每种情况的和的相反数与前
最相近的前后结果,更新最小值即可,这个寻找最接近的数可以通过二分查找来实现。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
#define abs(x) ((x) >= 0 ? (x) : -(x))
#define mp make_pair
typedef long long ll;
ll a[40];
int main()
{
int n;
while (scanf("%d", &n) != EOF && n)
{
for (int i = 0; i < n; i++)
scanf("%lld", &a[i]);
pair<ll, ll> ans = mp(abs(a[0]), 1);
map<ll, ll> m;
int n1 = n >> 1, n2 = n - (n >> 1);
for (int i = 1; i < (1 << n1); i++)
{
ll sum = 0, cnt = 0;
for (int j = 0; j < n1; j++)
if ((i >> j) & 1)
sum += a[j], cnt++;
ll sum1 = abs(sum);
if (sum1 < ans.first || (sum1 == ans.first && cnt < ans.second))
ans = mp(sum1, cnt);
if (m[sum])
m[sum] = min(m[sum], cnt);
else
m[sum] = cnt;
}
for (int i = 1; i < (1 << n2); i++)
{
ll sum = 0, cnt = 0;
for (int j = 0; j < n2; j++)
{
if ((i >> j) & 1)
sum += a[j + n1], cnt++;
}
ll sum1 = abs(sum);
if (sum1 < ans.first || (sum1 == ans.first && cnt < ans.second))
ans = mp(sum1, cnt);
map<ll, ll>::iterator it = m.lower_bound(-sum);
if (it != m.end())
{
ll s = abs(it->first + sum), t = it->second + cnt;
if (s < ans.first || (s == ans.first && t < ans.second))
ans = mp(s, t);
}
if (it != m.begin())
{
it--;
ll s = abs(it->first + sum), t = it->second + cnt;
if (s < ans.first || (s == ans.first && t < ans.second))
ans = mp(s, t);
}
}
printf("%lld %lld\n", ans.first, ans.second);
}
return 0;
} 
京公网安备 11010502036488号