[POJ 3977] Subset

本文介绍了一种使用折半搜索算法解决最小绝对值子集和问题的方法,通过预处理一半数的组合并排序另一半,实现高效查找最接近零的子集和,适用于数列长度不超过35的情况。
Subset
Time Limit: 30000MS Memory Limit: 65536K
Total Submissions: 4863 Accepted: 892

Description

Given a list of N integers with absolute values no larger than 1015, 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.

Input

The input contains multiple data sets, the first line of each data set contains N <= 35, the number of elements, the next line contains N numbers no larger than 1015 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

Source

 
题目大意就是给你一个数,你要在其中跳出一些数,使他们的和的绝对值越小越好,在前者一样的情况下,要使选出数的个数也越小越好.
由于n只有35,如果用O(2^35)的算法是过不了的.显然,这题很符合折半搜索.我们可以压位将前一半的所有组合记录下来,用了2^(n/2)的时间.
然后,我们再将另一半排序.我们枚举每一个前一半的状态(注意是状态,也就是多个数的组合(当然也可以有1个数)),然后在后一半二分查找,使当前状态的sum与查找到的状态(注意也是状态)的和越接近0越好.
这一步效率是O(2^18*log2(2^18))的,也就是O(2^18*18)的,稳过.
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define LL long long
 5 using namespace std;
 6 int n,m,s1,s2;
 7 LL a[40],anssum,ansnum;
 8 struct data{
 9     LL v,c;
10     bool operator < (const data &u) const {
11         return v<u.v||v==u.v&&c>u.c;
12     }
13 }L[1<<20],R[1<<20];
14 LL abso(LL x){return x>0?x:-x;}
15 int bin_find_sml(int i){
16     int l=1,r=s1-1,mid,ret=1;
17     while (l<=r){
18         mid=(l+r)>>1;
19         if (L[mid].v==-R[i].v) return mid; else
20         if (L[mid].v<-R[i].v) ret=mid,l=mid+1; else r=mid-1;
21     }
22     return ret;
23 }
24 int main(){
25     while (scanf("%d",&n)&&n){
26         memset(a,0,sizeof a);
27         for (int i=0; i<n; i++) scanf("%lld",&a[i]); sort(a,a+n);
28         if (n==1){printf("%lld 1\n",abso(a[0])); continue;}
29         m=n/2,s1=1<<(m+1),s2=1<<(n-m-1),anssum=ansnum=1e18;
30         memset(L,0,sizeof L);
31         for (int i=0; i<s1; i++)
32             for (int j=0; j<=m; j++) if (i&(1<<j)) L[i|(1<<j)].v=L[i].v+a[j],L[i|(1<<j)].c=L[i].c+1;
33         memset(R,0,sizeof R);
34         for (int i=0; i<s2; i++)
35             for (int j=0; j<=n-m-2; j++) if (i&(1<<j)) R[i|(1<<j)].v=R[i].v+a[j+m+1],R[i|(1<<j)].c=R[i].c+1;
36         for (int i=0; i<s1; i++) if (abso(L[i].v)<=anssum&&L[i].c){
37             if (abso(L[i].v)<anssum) anssum=abso(L[i].v),ansnum=L[i].c;
38             else if (L[i].c<ansnum) ansnum=L[i].c;
39         }
40         for (int i=0; i<s2; i++) if (abso(R[i].v)<=anssum&&R[i].c){
41             if (abso(R[i].v)<anssum) anssum=abso(R[i].v),ansnum=R[i].c;
42             else if (R[i].c<ansnum) ansnum=R[i].c;
43         }
44         sort(L+1,L+s1); sort(R+1,R+s2);
45         for (int i=1; i<s2; i++){
46             int x=bin_find_sml(i);
47             while (x<s1){
48                 if (abso(R[i].v+L[x].v)<=anssum){
49                     if (abso(R[i].v+L[x].v)<anssum) anssum=abso(R[i].v+L[x].v),ansnum=R[i].c+L[x].c;
50                     else if (R[i].c+L[x].c<ansnum) ansnum=R[i].c+L[x].c;
51                 }else{
52                     if (x<s1-1){if (abso(R[i].v+L[x].v)<=abso(R[i].v+L[x+1].v)) break;}
53                     else if (x+1==s1) break;
54                 }
55                 x++;
56             }
57         }
58         printf("%lld %lld\n",anssum,ansnum);
59     }
60     return 0;
61 }
View Code

 

转载于:https://www.cnblogs.com/whc200305/p/7345364.html

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值