题意
传送门 POJ 3977
题解
取绝对值最小的和,且集合大小最小。
将数据集合折半枚举所有状态,一半排序后在另一半枚举时进行二分。一般而言用大于等于以及小于当前枚举数据和的相反数 − s u m -sum −sum 的值进行更新即可,并保证 非空集 且元素个数最小。考虑到 空集 的和为 0 0 0,要保证 − s u m -sum −sum 左右的数都能搜索到,在 s u m = 0 sum=0 sum=0 的时候要特殊处理。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define min(a,b) (((a) < (b)) ? (a) : (b))
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define abs(x) ((x) < 0 ? -(x) : (x))
#define INF 0x3f3f3f3f3f3f3f3f
#define delta 0.85
#define eps 1e-5
#define PI 3.14159265358979323846
#define MAX_N 40
using namespace std;
typedef long long LL;
typedef pair<LL, int> P;
int N, rn;
LL res;
LL S[MAX_N];
P H[1 << (MAX_N / 2)];
void update(P *p, LL s, int cnt){
LL s2 = s + p->first;
int n2 = cnt + p->second;
if(n2 > 0 && (abs(s2) < res || (abs(s2) == res && n2 < rn))){
res = abs(s2), rn = n2;
}
}
int main(){
while(~scanf("%d", &N) && N){
for(int i = 0; i < N; i++) scanf("%lld", S + i);
int half = N / 2;
for(int i = 0; i < 1 << half; i++){
LL s = 0;
int cnt = 0;
for(int j = 0; j < half; j++) if(1 & (i >> j)) s += S[j], ++cnt;
H[i] = P(s, cnt);
}
sort(H, H + (1 << half));
res = INF;
rn = MAX_N + 1;
for(int i = 0; i < 1 << (N - half); i++){
LL s = 0;
int cnt = 0;
for(int j = 0; j < N - half; j++) if(1 & (i >> j)) s += S[j + half], ++cnt;
// 求满足条件且元素个数最小的值
P *p = lower_bound(H, H + (1 << half), P(-s, 0));
// 特殊处理 0
if(s == 0) if(p + 1 - H < (1 << half)) update(p + 1, s, cnt);
if(p - H < (1 << half)) update(p, s, cnt);
--p;
if(p - H >= 0){
// 保证小于二分值相反数的和的元素个数最小
while(p - H - 1 >= 0 && p->first == (p - 1)->first) --p;
update(p, s, cnt);
}
}
printf("%lld %d\n", res, rn);
}
return 0;
}