折半枚举——poj3977
暴力搜索超时,但是折半后两部分状态支持合并的情况,可用折半枚举算法
poj3977 给一个序列a[],从里面找到k个数,使其和的绝对值最小
经典折半枚举法+二分解决,对于前一半数开一个map,map[sum]里存下凑出当前sum的最小元素个数
枚举后面一半的所有情况,然后lower_bound去找map里最接近-sum的元素,由于要求输出sum最小并且num也尽量小的答案,所以用pair来存答案
#include<iostream> #include<algorithm> #include<string.h> #include<stdio.h> #include<math.h> #include<vector> #include<map> using namespace std; #define N 45 #define PI 4*atan(1.0) #define mod 1000000007 #define met(a, b) memset(a, b, sizeof(a)) #define INF 10000000000000000 typedef long long LL; int n; LL a[N]; LL Abs(LL x){return x<0?-x:x;} int main(){ while(scanf("%d", &n), n){ for(int i=0; i<n; i++) scanf("%I64d", &a[i]); map<LL, int> M; map<LL, int>::iterator it; pair<LL, int> ans(Abs(a[0]), 1); for(int i=1; i<1<<(n/2); i++){ LL sum = 0;int cnt = 0; for(int j=0; j<(n/2); j++){ if((i>>j)&1){ sum += a[j]; cnt ++; } } ans = min(ans, make_pair(Abs(sum), cnt));///全部是前半部分的; if(M[sum])///更新cnt为小的; M[sum] = min(M[sum], cnt); else M[sum] = cnt; } for(int i=1; i<1<<(n-n/2); i++){ LL sum = 0;int cnt = 0; for(int j=0; j<(n-n/2); j++){ if((i>>j)&1){ sum += a[j+n/2]; cnt ++; } } ans = min(ans, make_pair(Abs(sum), cnt));///全部是后半部分的; it = M.lower_bound(-sum);///找到第一个大于-sum的位置,然后取两种情况的最小值; if(it != M.end()) ans = min(ans, make_pair(Abs(sum+it->first), cnt+it->second)); if(it != M.begin()){ it--; ans = min(ans, make_pair(Abs(sum+it->first), cnt+it->second)); } } printf("%I64d %d\n", ans.first, ans.second); } return 0; }