POJ 3977 Subset

Description

Given a list of N integers with absolute values no larger than 10 15, find a non empty subset of these numbers which minimizes the absolute value of the sum of its elements. In case there are multiple subsets, choose the one with fewer elements.

题意是给定n个数字,要求找到其一个非空子集,使得这个子集内绝对值的和最小。

类似的题目,由于很多数根本不会出现,而且数字非常大,一般来说都是折半搜索,先扫一边,在另一边用stl里的map查询有无,但是或许是因为自己写挫了的缘故,超时了。

所以最后我选择了用数组模拟,由于还有统计最少需要多少个,故开了一个结构体,v表示它的和,cnt表示构成它所需的次数,使用折半搜索的思想,折半先构造出前一半所能达到的和。接着构造后一半能达到的和sum,查找-sum是否能在前一半种找到,找不到就找最贴近的那一个。这时问题转换成了如何查找所有数内,与某个数最贴近的数,幸好c++算法库里自带lower_bound函数,算以我们可以在算出前一半时排序,在算后一半时边算达到的和sum,边查找-sum,由于最贴近的那一个如果-sum不存在,那就是大于-sum最小的值了,而需要的是绝对值,所以我们还要将这是lower_bound算出的pos-1,来求出小于-sum最大的值。注意lower_bound如果查不到返回的是右端点的值,所以如果这时pos如果是右端点,就给它-1,还要保证在求小于-sum最大的值时,pos-1是>0的。

下附AC代码

#include<iostream>
#include<algorithm>
#define maxn 40
using namespace std;
typedef long long ll ;
int n;
ll myabs(ll i)
{
	return i>=0 ? i : -i;
}
struct nod
{
	ll v;
	int cnt;
	nod(ll a,int b)
	{
		v=a;
		cnt=b;
	}
	nod(){}
}a[1<<20];
ll num[maxn];
bool operator <(nod i,nod j)
{
	if(i.v!=j.v)
	return i.v<j.v;
	return i.cnt<j.cnt;
}
int main()
{
	while(cin>>n && n)
	{
		ll cnt1=0,cnt2=0;
		for(int i=0;i<n;i++)
		cin>>num[i];
		
		nod ans=nod(myabs(num[0]),1);
		
		int mid=n>>1;
		
		for(ll i=0;i<(1<<mid);i++)
		{
			int ncnt=0;
			a[i].v=0;
			for(int j=0;j<mid;j++)
			if((i>>j) & 1)
			{
				a[i].v+=num[j];
				ncnt++;
			}
			a[i].cnt=ncnt;
			if(i)
			ans=min(ans,nod(myabs(a[i].v),a[i].cnt));
		}
		
		sort(a,a+(1<<mid));
		
		for(ll i=0;i<(1<<mid)-1;i++)
		if(a[i].v==a[i+1].v)
		{
			a[i+1].cnt=a[i].cnt;
		} 
		for(ll i=1;i<(1<<(n-mid));i++)
		{
			ll nsum=0;
			int ncnt=0;
			for(int j=0;j<n-mid;j++)
			if((i>>j) & 1)
			{
				nsum+=num[mid+j];
				ncnt++;
			}

			nod now=nod(myabs(nsum),ncnt);
			ans=min(ans,now);
			
			ll pos=lower_bound(a,a+(1<<mid),nod(-nsum,0))-a;
			for(int j=min(pos,(long long)((1<<mid)-1));j>=max(pos-1,0ll);j--)
			{
				ans=min(ans,nod(myabs(a[j].v+nsum),a[j].cnt+ncnt)); 
			}
		}
		cout<<ans.v<<' '<<ans.cnt<<endl;
	}
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值