POJ 3977 折半枚举

题意

传送门 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值