题目链接:https://vjudge.net/problem/POJ-3977
题意:给一个大小<=35的集合,找一个非空子集合,使得子集合元素和的绝对值最小,如果有多个这样的集合,找元素个数最少的。
思路:显然,可以用折半搜索,分别枚举一半,最大是2的18次方,复杂度能够满足。因为集合非空,枚举时考虑只在前一半选和只在后一半选的情况。对于前一半后一半都选的情况,把前一半的结果存下来,排序,枚举后一半的时候在前一半里二分查找最合适的即可。
思路不难,实现有很多细节,最开始用dfs写得一直wa,改成二进制循环才A。
AC代码:
#include<cstdio> #include<algorithm> #include<map> using namespace std; typedef long long LL; typedef pair<LL,int> PLI; const LL inf1=1e17; const int inf2=0x3f3f3f3f; int n,m,cnt,ans2; LL a[40],ans1; PLI b[140005]; map<LL,int> mp; LL absLL(LL x){ return x>=0?x:-x; } int main(){ while(scanf("%d",&n),n){ mp.clear(); cnt=0; ans1=inf1; ans2=inf2; for(int i=1;i<=n;++i) scanf("%lld",&a[i]); m=n/2; for(int i=1;i<(1<<m);++i){ LL res1=0; int res2=0; for(int j=0;j<m;++j) if((i>>j)&1){ res1+=a[j+1]; ++res2; } if(absLL(res1)<ans1){ ans1=absLL(res1),ans2=res2; } else if(absLL(res1)==ans1){ ans2=min(ans2,res2); } if(mp.count(res1)){ int tmp=mp[res1]; b[tmp].second=min(b[tmp].second,res2); } else{ b[++cnt].first=res1,b[cnt].second=res2; mp[res1]=cnt; } } sort(b+1,b+cnt+1); for(int i=1;i<(1<<(n-m));++i){ LL res1=0; int res2=0; for(int j=0;j<(n-m);++j) if((i>>j)&1){ res1+=a[j+1+m]; ++res2; } if(absLL(res1)<ans1){ ans1=absLL(res1),ans2=res2; } else if(absLL(res1)==ans1){ ans2=min(ans2,res2); } LL tmp=-res1; int l=1,r=cnt,mid; while(l<=r){ mid=(l+r)>>1; if(b[mid].first<=tmp) l=mid+1; else r=mid-1; } if(r>0){ if(absLL(b[r].first+res1)<ans1){ ans1=absLL(b[r].first+res1); ans2=b[r].second+res2; } else if(absLL(b[r].first+res1)==ans1){ ans2=min(ans2,b[r].second+res2); } } if(r+1<=cnt){ if(absLL(b[r+1].first+res1)<ans1){ ans1=absLL(b[r+1].first+res1); ans2=b[r+1].second+res2; } else if(absLL(b[r+1].first+res1)==ans1){ ans2=min(ans2,b[r+1].second+res2); } } } printf("%lld %d\n",ans1,ans2); } return 0; }