POJ 3977 折半枚举

链接:

http://poj.org/problem?id=3977

题意:

给你n个数,n最大35,让你从中选几个数,不能选0个,使它们和的绝对值最小,如果有一样的,取个数最小的 

思路:

子集个数共有2^n个,所以不能全部枚举,但是可以分为两部分枚举;枚举一半的所有情况,然后后一半二分即可;

代码:

 1 #include"bits/stdc++.h"
 2 #define N 45
 3 using namespace std;
 4 typedef long long LL;
 5 
 6 int n;
 7 LL a[N];
 8 
 9 LL Abs(LL x)
10 {
11     return x<0?-x:x;
12 }
13 
14 int main()
15 {
16     while(scanf("%d", &n), n)
17     {
18         for(int i=0; i<n; i++)
19             scanf("%I64d", &a[i]);
20 
21         map<LL, int> M;
22         map<LL, int>::iterator it;
23         pair<LL, int> ans(Abs(a[0]), 1);
24 
25         for(int i=1; i<1<<(n/2); i++)
26         {
27             LL sum = 0;int cnt = 0;
28             for(int j=0; j<(n/2); j++)
29             {
30                 if((i>>j)&1)
31                 {
32                     sum += a[j];
33                     cnt ++;
34                 }
35             }
36             ans = min(ans, make_pair(Abs(sum), cnt));///全部是前半部分的;
37             if(M[sum])///更新cnt为小的;
38                 M[sum] = min(M[sum], cnt);
39             else
40                 M[sum] = cnt;
41         }
42 
43         for(int i=1; i<1<<(n-n/2); i++)
44         {
45             LL sum = 0;int cnt = 0;
46             for(int j=0; j<(n-n/2); j++)
47             {
48                 if((i>>j)&1)
49                 {
50                     sum += a[j+n/2];
51                     cnt ++;
52                 }
53             }
54             ans = min(ans, make_pair(Abs(sum), cnt));///全部是后半部分的;
55 
56             it = M.lower_bound(-sum);///找到第一个大于-sum的位置,然后取两种情况的最小值;
57 
58             if(it != M.end())
59                 ans = min(ans, make_pair(Abs(sum+it->first), cnt+it->second));
60             if(it != M.begin())
61             {
62                 it--;
63                 ans = min(ans, make_pair(Abs(sum+it->first), cnt+it->second));
64             }
65         }
66         printf("%I64d %d\n", ans.first, ans.second);
67     }
68     return 0;
69 }

猜你喜欢

转载自www.cnblogs.com/mj-liylho/p/9453395.html