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

/ 0评 /

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

题目链接:POJ 3977 Subset

Description

Given a list of N integers with absolute values no larger than 10^{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 N \leqslant 35, the number of elements, the next line contains N numbers no larger than 10^{15} in absolute value and separated by a single space. The input is terminated with N = 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

看到这题的数据范围:N \leqslant 35……在我知道折半搜索这个玩意儿之前,我对这种大不大小不小的 Topcoder 式数据范围是手足无措的,因为如果直接状压暴力枚举是 2^{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 以及各种网站问题不要来找我!!不然我的时间就会被一下午一下午地续掉……除非你请我喝星巴克


知识共享许可协议 本文章采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
欢迎转载,如有错误欢迎指出。
本文链接:https://skywt.cn/posts/poj3977/


发表评论

电子邮件地址不会被公开。 必填项已用*标注