poj_3977 折半枚举

题目大意

    给定N(N<=35)个数字,每个数字都<= 2^15. 其中一个或多个数字加和可以得到s,求出s的绝对值的最小值,并给出当s取绝对值最小值时,需要加和的数字的个数。

题目分析

    需要枚举集合的所有情况,2^35,会超时。考虑使用折半枚举的方法,考虑前 N/2个数字构成的集合S1,在S1中进行所有情况枚举,复杂度为 2^17,并将所有可能的和sum以及构成和sum需要的数字个数count存放在map M中;然后在S2中进行所有情况的枚举,复杂度为2^17,对于每种情况的sum2,在M中查找 -sum2的位置,在该位置前后位置处进行查找,求和的最小值。 
    还需要考虑,当s只有S1中的数字构成或者s只有S2中的数字构成,或者s由S1和S2中的数字构成的三类情况。 
    总的时间复杂度为 O(2^17 + 2^17*log(2^17)) = O(2^22)

实现(c++)

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
#include<map>
using namespace std;

long long int ll_abs(long long int n){
	if (n >= 0)
		return n;
	return -n;
}
long long int an[40];

map<long long int, int> sum_map;
int main2(){
	int n;
	while (scanf("%d", &n) && n){
		map<long long int, int>::iterator it;
		

		sum_map.clear();
		for (int i = 0; i < n; i++){
			scanf("%lld", &an[i]);
		}
		long long int min_sum = ll_abs(an[0]);
		int min_count = 1;

		int m = n / 2;
		for (int i = 0; m > 0 && i < (1 << m); i++){
			long long int sum = 0;
			int count = 0;
			int t = i;
			for (int k = 0; k < m; k++){
				if (t & 1){
					sum += an[k];
					count++;
				}
				t >>= 1;
			}
			if (count == 0)
				continue;

			if (sum_map.find(sum) != sum_map.end()){
				sum_map[sum] = min(sum_map[sum], count);
			}
			else
				sum_map[sum] = count;
			if (ll_abs(sum) < min_sum){
				min_sum = ll_abs(sum);
				min_count = count;
			}
			else if (ll_abs(sum) == min_sum){
				min_count = min(min_count, count);
			}
		}
		m = n / 2 + n % 2;
		for (int i = 0; i < (1 << m); i++){
			long long int sum = 0;
			int count = 0;
			int t = i;
			for (int k = 0; k < m; k++){
				if (t & 1){
					sum += an[n / 2 + k];
					count++;
				}
				t >>= 1;
			}
			if (count == 0)
				continue;


			if (ll_abs(sum) < min_sum){
				min_sum = ll_abs(sum);
				min_count = count;
			}
			else if (ll_abs(sum) == min_sum){
				min_count = min(min_count, count);
			}

			it = sum_map.lower_bound(-sum);
			if (it != sum_map.end()){
				long long int s = sum + it->first;
				if (ll_abs(s) < min_sum){
					min_sum = ll_abs(s);
					min_count = it->second + count;
				}
				else if (ll_abs(s) == min_sum){
					min_count = min(min_count, it->second + count);
				}
			}
			if (it != sum_map.begin()){
				--it;
				long long int s = sum + it->first;
				if (ll_abs(s) < min_sum){
					min_sum = ll_abs(s);
					min_count = it->second + count;
				}
				else if (ll_abs(s) == min_sum){
					min_count = min(min_count, it->second + count);
				}
			}
		}

		printf("%lld %d
", min_sum, min_count);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/gtarcoder/p/4909448.html