SkyWT / 博客 / POJ 3977 Subset 题解:折半搜索+二分查找

POJ 3977 Subset 题解:折半搜索+二分查找

2018 年 9 月 11 日 11:58


文章目录

这题思路很简单,但是调试花了我一个下午……最后发现是一种情况没取 abs……(吐血)

题目链接:POJ 3977 Subset

Description

Given a list of NN integers with absolute values no larger than 101510^{15}, find a non empty subset of these numbers which minimizes the absolute value of the sum of its elements. In case there are multiple subsets, choose the one with fewer elements.

Time Limit: 30000MS
Memory Limit: 65536K

Input

The input contains multiple data sets, the first line of each data set contains N35N \leqslant 35, the number of elements, the next line contains NN numbers no larger than 101510^{15} in absolute value and separated by a single space. The input is terminated with N=0N = 0.

Output

For each data set in the input print two integers, the minimum absolute sum and the number of elements in the optimal subset.

Sample Input

1
10
3
20 100 -100
0

Sample Output

10 1
0 2

Analysis

看到这题的数据范围:N35N \leqslant 35……在我知道折半搜索这个玩意儿之前,我对这种大不大小不小的 Topcoder 式数据范围是手足无措的,因为如果直接状压暴力枚举是 2352^{35},显然不能承受;但是 35 这个数字对于其他做法来说又太小了……
如果我们能把 35 折半该多好!那 N 就是 16 或者 17,直接状压构造就可以承受了。
所以这就有了:折半搜索

我们可以把这个数列分成两半,先暴力构造右边所有数字组合的加和并保存、排序,然后暴力构造左边,对于左边每个组合加和只要去右边二分查找一下就可以了。

Code

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const long long maxn=40,maxs=400005,INF=(long long)1e15+100;
long long n,sl,sr,m;
long long a[maxn];

struct Element{
    long long num,count;
};
Element s[maxs],ans;

inline long long Abs(long long x){
    return x<0?-x:x;
}

inline bool compare(Element aa,Element bb){
    return (aa.num<bb.num)||((aa.num==bb.num)&&(aa.count<bb.count));
}

inline Element find(long long x){
    long long L=1,R=m;
    Element ret;
    ret.num=INF;ret.count=0;
    while (L<=R){
        long long mid=((R-L)>>1)+L;
        if ((Abs(s[mid].num+x)<ret.num)||(Abs(s[mid].num+x)==ret.num&&s[mid].count<ret.count)) ret=(Element){Abs(s[mid].num+x),s[mid].count};
        if (s[mid].num<-x) L=mid+1; else R=mid-1;
    }
    return ret;
}

inline void make_right(){
    m=0;
    for (long long i=1;i<sr;i++){
        long long now_sum=0,now_cnt=0;
        for (long long j=0;j<n/2;j++) if (i&(1<<j)) now_sum+=a[n-j],now_cnt++;
        s[++m]=(Element){now_sum,now_cnt};
    }
}

inline void make_left(){
    sort(s+1,s+1+m,compare);
    ans.num=INF;ans.count=0;
    for (long long i=0;i<sl;i++){
        long long now_sum=0,now_cnt=0;
        for (long long j=0;j<n-(n/2);j++) if (i&(1<<j)) now_sum+=a[j+1],now_cnt++;
        Element f=find(now_sum);
        if ((f.num<ans.num)||((f.num==ans.num)&&(f.count+now_cnt<ans.count))) ans=(Element){f.num,f.count+now_cnt};
        if (now_cnt==0) continue;
        if ((Abs(now_sum)<ans.num)||((Abs(now_sum)==ans.num)&&(now_cnt<ans.count))) ans=(Element){Abs(now_sum),now_cnt};
    }
}

int main(){
    while (~scanf("%lld",&n)){
        if (n==0) break;
        for (long long i=1;i<=n;i++) scanf("%lld",&a[i]);
        sr=(long long)1<<(n/2);
        sl=(long long)1<<(n-(n/2));
        make_right();
        make_left();
        printf("%lld %lld\n",ans.num,ans.count);
    }
    return 0;
}

警告 @Feluamn,以后遇到 WordPress 以及各种网站问题不要来找我!!不然我的时间就会被一下午一下午地续掉……除非你请我喝星巴克


暂无评论


发表新的评论

所有评论都将经过博主审核。请勿填写无意义邮箱或发表无关评论、广告等,否则会被视为垃圾评论。

提交评论即表明你同意本网站使用 Cookie,并允许本站在后台记录你的邮箱、IP 地址等必要信息。这些信息不会被透露给其他用户。(提交一次评论后,本提示将不再展示)