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\n",ans.first,ans.second); }return 0; }
愿你出走半生,归来仍是少年