poj3977 折半枚举

懵懂的女人 提交于 2020-01-05 04:43:54

传送门:https://vjudge.net/problem/POJ-3977

题意:给你n数(n<=35),从中选出一个非空子集,使得这个子集的所有元素的值的和的绝对值最小,如果有多组数据满足的话,选择子集元素最少的那个。

这题是从挑战程序设计竞赛来的。就是折半枚举。也就是我先分别枚举前面一半的选不选状态(最多2的17次方,O(能过)),和后面一半的,得到两个子集和的数列。子集要么是只从前面一半选,要么只从后面一半选,要么从前面和后面。前面两种情况可以在枚举的时候搞了。至于最后那种情况,我们可以这样解决:就是先对两个枚举出来的子集和数列排个序,然后枚举第一个数列,对于枚举的第一个数列的子集和sum,我们要让它加上从第二个数列的子集和的绝对值最小,也就是在第二个数列中找-sum。这个由于排过序了,所以可以二分。nlogn,可过。

要注意的地方是:首先,不能是空集,所以每次更新答案前要判断它是否空。

其次:我在找第二个数列找-sum的时候,用lower_bound找到p,但是可能找到的数比-sum大的,所以还要比较一下p-1那个数。具体看代码吧。

 

还有,这题再次验证了一个定律。就是每当我卡题的时候,只要我说一句“不过hjj××”,这题就一定AC。这题就是最好的证明了。。。。。

 1 // Cease to struggle and you cease to live
 2 #include <iostream>
 3 #include <cmath>
 4 #include <cstdio>
 5 #include <cstring>
 6 #include <algorithm>
 7 #include <queue>
 8 #include <vector>
 9 #include <set>
10 #include <map>
11 #include <stack>
12 using namespace std;
13 typedef long long ll;
14 ll a[40];
15 #define pii pair<ll,int> 
16 #define mpr make_pair
17 pii num1[(int)3e5],num2[(int)3e5];
18 ll _abs(ll a){return a>0?a:-a;}
19 int main() {
20     int n;
21     while(~scanf("%d",&n)){
22         if(!n) break;
23         pii ans=mpr(100000000000000000,0x3f3f3f3f);
24         for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
25         int a1=n/2,a2=n-a1;
26         for(int i=0;i<(1<<a1);++i){
27             num1[i].first=num1[i].second=0;
28             for(int j=1;j<=a1;++j){
29                 if((i>>(a1-j))&1){
30                     num1[i].first+=a[j],++num1[i].second; 
31                 }
32             }
33             pii tem=mpr(_abs(num1[i].first),num1[i].second);
34             if(tem.second && tem<ans) ans=tem;
35         }
36         for(int i=0;i<(1<<a2);++i){
37             num2[i].first=num2[i].second=0;
38             for(int j=1;j<=a2;++j){
39                 if((i>>(a2-j))&1) num2[i].first+=a[j+a1],++num2[i].second;
40             }
41             pii tem=mpr(_abs(num2[i].first),num2[i].second);
42             if(tem.second && tem<ans) ans=tem;
43         }
44         //cerr<<ans.first<<ans.second<<endl;
45         sort(num1,num1+(1<<a1));
46         sort(num2,num2+(1<<a2));
47         for(int i=0;i<(1<<a1);++i){
48             ll c=num1[i].first;
49             int p=lower_bound(num2,num2+(1<<a2),mpr(-c,-1))-num2;
50             if(p!=0){
51                 pii tem=mpr(_abs(num1[i].first+num2[p-1].first),num1[i].second+num2[p-1].second);
52                 if(tem.second && tem<ans) ans=tem;
53             }
54             //cerr<<ans.first<<endl;
55             if(p!=(1<<a2)){
56                 pii tem=mpr(_abs(num1[i].first+num2[p].first),num1[i].second+num2[p].second);
57                 //cerr<<num1[i].first<<' '<<num2[p].first<<' '<<p<<endl;
58                 if(tem.second && tem<ans) ans=tem;
59             }
60             //cerr<<ans.first<<endl;
61         }
62         printf("%lld %d\n",ans.first,ans.second);
63     }
64     return 0;
65 }
View Code

最近训练的有些松散,接下来一定勤加练习。

 

睿。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!