传送门: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 }
最近训练的有些松散,接下来一定勤加练习。
睿。
来源:https://www.cnblogs.com/xiaobuxie/p/10850420.html
