poj3977(折半枚举,多坑)

/*
translation:
	给出一列数列,找出其中的非空连续子序列,使得其和的绝对值最小。如果有相同的和的情况下输出元素个数最少的那个
solution:
	折半枚举即可
note:
	#: 思路很简单,分成两半,折半枚举即可,但是代码中有很多坑。首先必须对前后两部分只选一个的情况单独考虑。然后如果
		ans_sum的预设值为INF的话还是会WA,所以最好设置为随便一个集合的和就行。
date:
	2016.11.12
*/
#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
#include <cmath>

using namespace std;
const int maxn = 36;
const int INF = 1e30;

typedef long long ll;

map<ll, ll> sum_set;	//first:sum    second:numbers of elements
ll a[maxn];	int n;

ll abs1(ll x)
{
	return x < 0 ? -x : x;
}

int main()
{
	//freopen("in.txt", "r", stdin);
	while(cin >> n && n){
		sum_set.clear();
		for(int i = 0; i < n; i++)	cin >> a[i];

		int fn = n / 2;
		int bn = n - fn;
		ll ans_sum = a[0], ans_cnt = 1;

		for(int i = 0; i < (1 << fn); i++){	//预处理前半部分
			ll num = 0, sum = 0;
			for(int j = 0; j < fn; j++){
				if(i >> j & 1){
					num++;
					sum += a[j];
				}
			}

			if(num == 0)	continue;
			if(abs1(ans_sum) > abs1(sum) || (abs1(ans_sum) == abs1(sum) && ans_cnt > num)){
				ans_sum = sum;
				ans_cnt = num;
			}

			map<ll, ll>::iterator it = sum_set.find(sum);
			if(it != sum_set.end()){
				it->second = min(it->second, num);
			}else{
				sum_set[sum] = num;
			}
		}

		//枚举后半部分
		for(int i = 0; i < (1 << bn); i++){
			ll num = 0, sum = 0;
			for(int j = 0; j < bn; j++){
				if(i >> j & 1){
					num++;
					sum += a[j+fn];
				}
			}

			if(num == 0)	continue;
			if(abs1(ans_sum) > abs1(sum) || (abs1(ans_sum) == abs1(sum) && ans_cnt > num)){
				ans_sum = sum;
				ans_cnt = num;
			}

			//在已经预处理过的前半部分寻找最为接近sum的值
			map<ll, ll>::iterator it = sum_set.lower_bound(-sum);

			if(it != sum_set.end()){
				if(abs1(ans_sum) > abs1(it->first + sum) ||
					(abs1(ans_sum) == abs1(it->first + sum) && ans_cnt > it->second + num)){
						ans_sum = abs1(it->first + sum);
						ans_cnt = it->second + num;
					}
			}

			if(it != sum_set.begin()){
				it--;
				if(abs1(ans_sum) > abs1(it->first + sum) ||
					(abs1(ans_sum) == abs1(it->first + sum) && ans_cnt > it->second + num)){
						ans_sum = abs1(it->first + sum);
						ans_cnt = it->second + num;
					}
			}
		}

		cout << abs1(ans_sum) << " " << ans_cnt << endl;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值