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;
}