题目链接: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;
}