传送门: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