poj 3977 Subset(折半枚举+二进制枚举+二分)

本文介绍了一种解决子集求和问题的高效算法,通过折半枚举和二分查找的方法,在面对大量数据时仍能快速找到使元素和绝对值最小的非空子集。
Subset
Time Limit: 30000MS Memory Limit: 65536K
Total Submissions: 5721 Accepted: 1083

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.

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 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

Source

 

题意:给你一个含n(n<=35)个数的数组,让你在数组中选出一个非空子集,使其元素和的绝对值最小,输出子集元素的个数以及元素和的绝对值,若两个子集元素和相等,输出元素个数小的那个。

思路:如果直接暴力枚举,复杂度O(2^n),n为35时会超时,故可以考虑折半枚举,利用二进制将和以及元素个数存在两个结构体数组中,先预判两个结构体是否满足题意,再将其中一个元素和取相反数后排序,因为总元素和越接近零越好,再二分查找即可,用lower_bound时考虑查找到的下标和他前一个下标,比较元素和以及元素个数,不断更新即可。

详见代码注释

poj的long long abs要自己写

 

  1 #include<cstdio>
  2 #include<iostream>
  3 #include<algorithm>
  4 #include<cmath>
  5 using namespace std;
  6 struct Z 
  7 {
  8     long long int x;
  9     int y;
 10     bool operator < (const Z& b)const
 11     {
 12         if (x != b.x)
 13             return x < b.x;
 14         return y<b.y;
 15 
 16     }
 17 }a[300005], b[300005];
 18 
 19 long long  int c[40];
 20 
 21 long long int abs1(long long int x) 
 22 {
 23     if (x<0)
 24         return -x;
 25     return x;
 26 }
 27 
 28 int main()
 29 {
 30     int n;
 31     int i,j;
 32     while (cin >> n && n)
 33     {
 34         for (i = 0; i < 300005; i++)
 35         {
 36             a[i].x = a[i].y = b[i].x = b[i].y = 0;
 37         }
 38         long long sum = 1e17;
 39         int ans = 40;
 40         for (i = 0; i < n; i++)
 41         {
 42             cin >> c[i];
 43         }
 44         int n1 = n / 2;
 45         for (i = 0; i < (1 << n1); i++)//二进制枚举一半,共2的n1次方种
 46         {
 47             for (j = 0; j < n1; j++)
 48             {
 49                 if (i >> j&1 && (i != 0 || j != 0))//这一半中的所有情况列出来
 50                 {
 51                     a[i - 1].x+= c[j];
 52                     a[i - 1].y++;//记录这个数含有几个元素
 53                 }
 54             }
 55         }
 56         int n2 = n - n1;
 57         for (i = 0; i < (1 << n2); i++)//同理初始化
 58         {
 59             for (j = 0; j < n2; j++)
 60             {
 61                 if (i >> j & 1 && (i != 0 || j != 0))
 62                 {
 63                     b[i - 1].x += c[j + n1];
 64                     b[i - 1].y++;
 65                 }
 66             }
 67         }
 68         //对这两半单独检查更新最小和sum和最小元素数ans
 69         for (i = 0; i < (1 << n1) - 1; i++)//
 70         {
 71             if (abs1(a[i].x) < sum)
 72             {
 73                 sum = abs1(a[i].x);
 74                 ans = a[i].y;
 75             }
 76             else if (abs1(a[i].x) == sum && a[i].y < ans)
 77             {
 78                 ans=a[i].y;
 79                 sum = abs1(a[i].x);
 80             }
 81         }
 82 
 83         for (i = 0; i<(1 << n1) - 1; i++)//前半部分变为相反数
 84             a[i].x = -a[i].x;
 85         for (i = 0; i<(1 << n2) - 1; i++) //另一半检查
 86         {
 87             if (abs1(b[i].x)<sum) 
 88             {
 89                 sum = abs1(b[i].x);
 90                 ans = b[i].y;
 91             }
 92             else if (abs1(b[i].x) == sum && b[i].y<ans) 
 93             {
 94                 ans = b[i].y;
 95                 sum = abs1(b[i].x);
 96             }
 97         }
 98 
 99         sort(a, a + (1 << n1) - 1);
100         sort(b, b + (1 << n2) - 1);
101 
102         for (i = 0; i < (1 << n1)-1; i++)//两半合起来检查
103         {
104             int t = lower_bound(b, b + (1 << n2) - 1, a[i])- b;//t是序号
105             if (t > 0)//查看该序号周围的数
106             {
107                 if (abs1(b[t - 1].x - a[i].x) < sum)//和可以更小
108                 {
109                     sum = abs1(b[t - 1].x - a[i].x);//更新最小绝对值和
110                     ans = b[t - 1].y + a[i].y;//更新元素个数
111                 }
112                 else if (abs1(b[t - 1].x - a[i].x) == sum && b[t - 1].y + a[i].y < ans)//元素个数可以更小
113                 {
114                     sum = abs1(b[t - 1].x - a[i].x);
115                     ans = b[t - 1].y + a[i].y;
116                 }
117             }
118             if (t < (1 << n2) - 1)
119             {
120                 if (abs1(b[t].x - a[i].x) < sum)
121                 {
122                     sum = abs1(b[t].x - a[i].x);
123                     ans = b[t].y + a[i].y;
124                 }
125                 else if (abs1(b[t].x - a[i].x) == sum && b[t].y + a[i].y<ans) 
126                 {
127                     sum = abs1(b[t].x - a[i].x);
128                     ans = b[t].y + a[i].y;
129                 }
130             }
131         }
132         cout << sum << " " << ans << endl;
133     }
134     return 0;
135 }

 

 

 

转载于:https://www.cnblogs.com/caiyishuai/p/8465416.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值