题目链接: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;
}
02-10 08:54