poj3977 折半枚举

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

时间: 2024-11-10 22:14:47

poj3977 折半枚举的相关文章

折半枚举——poj3977

暴力搜索超时,但是折半后两部分状态支持合并的情况,可用折半枚举算法 poj3977 给一个序列a[],从里面找到k个数,使其和的绝对值最小 经典折半枚举法+二分解决,对于前一半数开一个map,map[sum]里存下凑出当前sum的最小元素个数 枚举后面一半的所有情况,然后lower_bound去找map里最接近-sum的元素,由于要求输出sum最小并且num也尽量小的答案,所以用pair来存答案 #include<iostream> #include<algorithm> #inc

Load Balancing 折半枚举大法好啊

Load Balancing 给出每个学生的学分.   将学生按学分分成四组,使得sigma (sumi-n/4)最小.         算法:   折半枚举 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #include <string> 7 #include <

lightoj 1235 Coin Change (IV)(折半枚举)

话说这是俺们学校暑假集训完的一道题,刚看到以为是到水题,后来发现者复杂度也太大了,受不了了,比赛完也没搞出来,然后欣爷说这是折半枚举.然后就摸摸的学了一下,又把这道题写了一下, 所谓折半枚举就是先算出来一半,再算一半,然后用二分查找看看能不能搞到这一发状态,可以的话就是可以了, 题意:给你两个数n,k,下面再给你n个数,表示你现在有的硬币的面值,每种硬币面值的有两个,看是否可以支付k 题解思路:首先以为只有三种状态,直接dfs就好了,后来发现复杂度太大了,然后死了撸就是上面的,详细情残考代码 源

CSU OJ PID=1514: Packs 超大背包问题,折半枚举+二分查找。

1514: Packs Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 61  Solved: 4[Submit][Status][Web Board] Description Give you n packs, each of it has a value v and a weight w. Now you should find some packs, and the total of these value is max, total of

HDU 5340 Three Palindromes( 折半枚举+Manacher+记录区间 )

Three Palindromes Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Submission(s): 809    Accepted Submission(s): 240 Problem Description Can we divided a given string S into three nonempty palindromes? Input F

Eqs 折半枚举+二分查找 大水题

Eqs 题目抽象:a1x13+ a2x23+ a3x33+ a4x43+ a5x53=0 (*),给出a1,a2,a3,a4,a5.    ai属于[-50,50]. 求有多少序列   x1,x2,x3,x4,x5 ,xi属于 [-50,50]-{0}. 思路:折半枚举+二分查找 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 #inclu

poj 2785 4 Values whose Sum is 0 折半枚举

题目链接:http://poj.org/problem?id=2785 枚举的一般思路就是先把所有的状态枚举出来 最后一次性判断该状态合法不合法 而折半枚举的思想是 先枚举一半的状态 把他们的状态存起来 排序 然后再枚举剩下一般 用目标反推前一半的期望状态 接下来在前一半的结果数组中查找是否有相应结果 之所以能优化是因为结果数组有序 就可以用二分搜索 复杂度从O(n^2 * n^2) 降到 O(n^2 * log(n^2))即(O(n^2 * log n)) 二分搜索的一个技巧 在有序数组中用二

LightOj 1076 - Get the Containers (折半枚举 绝世好题)

题目链接: http://www.lightoj.com/volume_showproblem.php?problem=1076 题目描述: 给出n个数,要求分成m段,问这m段中最大的总和,最小是多少? 解题思路: 二分每一段的长度,然后判定枚举长度是否合法. 刚看到这个题目就想到了dp,但是dp的复杂度还是很高的,然后就一直放着,放着....学了折半枚举,拿起来一看,哇塞,简直惊喜啊! 1 #include<cstdio> 2 #include<cstring> 3 #inclu

NYOJ 1091 超大01背包(折半枚举)

这道题乍一看是普通的01背包,最最基础的,但是仔细一看数据,发现普通的根本没法做,仔细观察数组发现n比较小,利用这个特点将它划分为前半部分和后半部分这样就好了,当时在网上找题解,找不到,后来在挑战程序设计上找到了这个题,就拿来引用一下 挑选物品的方法总从2^n中,直接枚举肯定不行,因为n最大为40,但是如果n为20就可以了,这时候就要用到折半枚举,先枚举前一半,在枚举后一半.先把前把部分的选取方法对应的重量和价值总和记为w1, v1,这样后半部分寻找w2 <= W - w1时 使v2最大的选取方