POJ 3977:Subset(折半枚举+二分)

【题目链接】 http://poj.org/problem?id=3977

【题目大意】

  在n个数(n<36)中选取一些数,使得其和的绝对值最小.

【题解】

  因为枚举所有数选或者不选,复杂度太高无法承受,
  我们考虑减小枚举的范围,我们将前一半进行枚举,保存其子集和,
  然后后一半枚举子集和取反在前一半中寻找最接近的,两部分相加用以更新答案。

【代码】

#include <cstdio>
#include <utility>
#include <algorithm>
#include <map>
using namespace std;
typedef long long LL;
const int N=40; 
int n; LL a[N];
LL Abs(LL x){return x<0?-x:x;}
int main(){
    while(scanf("%d",&n)&&n){
        for(int i=0;i<n;i++)scanf("%lld",&a[i]);
        map<LL,int> M;
        map<LL,int>::iterator it;
        pair<LL,int> ans(Abs(a[0]),1);
        for(int i=1;i<1<<(n/2);i++){
            LL s=0; int cnt=0;
            for(int j=0;j*2<n;j++){if((i>>j)&1)s+=a[j],cnt++;}
            ans=min(ans,make_pair(Abs(s),cnt));
            if(M[s])M[s]=min(M[s],cnt);
            else M[s]=cnt;
        }
        for(int i=1;i<1<<(n-n/2);i++){
            LL s=0; int cnt=0;
            for(int j=0;j<(n-n/2);j++){
                if((i>>j)&1)s+=a[j+n/2],cnt++;
            }ans=min(ans,make_pair(Abs(s),cnt));
            it=M.lower_bound(-s);
            if(it!=M.end())ans=min(ans,make_pair(Abs(s+it->first),cnt+it->second));
            if(it!=M.begin()){
                it--;
                ans=min(ans,make_pair(Abs(s+it->first),cnt+it->second));
            }
        }printf("%lld %d
",ans.first,ans.second);
    }return 0;
}

  

原文地址:https://www.cnblogs.com/forever97/p/poj3977.html