Subset POJ - 3977(折半枚举+二分查找)

本文针对给定整数集合寻找子集使元素和的绝对值最小的问题,提出了一种通过分治并结合二分查找技术的有效算法。该算法能够显著减少搜索空间,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来源:Subset

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

题意

给定n个数,求这n个数所构成的集合的某个子集,使得这个子集中所有数和的绝对值最小,输出和的绝对值以及集合中数的个数。

思路

n是小于35的,直接枚举集合复杂度为2n,超时。可以枚举前n/2个数的集合,然后枚举后n/2个数的集合再在前面的集合中二分查找,这样复杂度为2(n/2) log(n/2)。

代码

#include <iostream>
#include <map>
#include <algorithm>
using namespace std;
inline long long Abs(long long x)
{
    return x > 0 ? x : -x;
}
int n;
long long a[40];
int main()
{
    while (cin >> n)
    {
        if (n == 0)
            break;
        for (int i = 1; i <= n; ++i)
            cin >> a[i];
        map<long long, int>mmp;
        long long ans = Abs(a[1]);
        int len = n;
        for (int i = 1; i < (1 << (n / 2)); ++i)
        {
            long long sum = 0;
            int j = i, cnt = 0, pos = 1;
            while (j && pos <= n / 2)
            {
                if (j & 1)
                {
                    sum += a[pos];
                    cnt++;
                }
                j >>= 1;
                pos++;
            }
            if (Abs(sum) < ans)
            {
                ans = Abs(sum);
                len = cnt;
            }
            else if (Abs(sum) == ans)
            {
                len = min(len, cnt);
            }

            if (mmp[sum])
                mmp[sum] = min(mmp[sum], cnt);
            else
                mmp[sum] = cnt;
        }
        for (int i = 1; i < (1 << (n - n / 2)); ++i)
        {
            long long sum = 0;
            int cnt = 0, pos = 1, j = i;
            while (j && pos + n / 2 <= n)
            {
                if (j & 1)
                {
                    sum += a[pos + n / 2];
                    cnt++;
                }
                j >>= 1;
                pos++;
            }
            
            if (Abs(sum) < ans)
            {
                ans = Abs(sum);
                len = cnt;
            }
            else if (Abs(sum) == ans)
            {
                len = min(len, cnt);
            }
            map<long long, int>::iterator it = mmp.lower_bound(-sum);
            if (it != mmp.end())
            {
                if (Abs(sum + it->first) < ans)
                {
                    ans = Abs(sum + it->first);
                    len = cnt + it->second;
                }
                else if (Abs(sum + it->first) == ans)
                {
                    len = min(len, cnt + it->second);
                }
            }
            if (it != mmp.begin())
            {
                it--;
                if (Abs(sum + it->first) < ans)
                {
                    ans = Abs(sum + it->first);
                    len = cnt + it->second;
                }
                else if (Abs(sum + it->first) == ans)
                {
                    len = min(len, cnt + it->second);
                }
            }
        }
        cout << Abs(ans) << ' ' << len << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值