有n个砝码,现在要称一个质量为m的物体,请问最少需要挑出几个砝码来称?
注意一个砝码最多只能挑一次
第一行两个整数n和m,接下来n行每行一个整数表示每个砝码的重量。
输出选择的砝码的总数k,你的程序必须使得k尽量的小。
3 10
5
9
1
2
1<=n<=30,1<=m<=2^31,1<=每个砝码的质量<=2^30
类型:其他 难度:2
题意:给出n个数,要求从中挑出k个数,使它们的和为m,求最小的k
分析:一个简单的思路:
1、就是把n个数分成两份,分别求每份的数所有可能取的和,用cnt1和cnt2两个哈希表记录,其中cnt1[i]=j表示最少需要凑出和为i,至少需要j个数。
遍历方法可以用二进制表示每个数是否取,比如有5个数,那么遍历1 - (2^5-1),每个数二进制为1,则将这个数累加,将累加的和作为哈希表的key,选取的个数作为哈希表的value,若比之前的小或是新出现的则更新哈希表对应项
2、对于两个哈希表cnt1和cnt2,遍历其中一个哈希表,设当前遍历的项为cnt1[i]=j,那么找cnt2[m-i]=k是否存在,若存在,那么j+k即为总共需要取的个数,记录最小值即为所求
3、最后分别看cnt1[m]和cnt2[m]是否存在,就是单独取两部分的和就能构成结果,更新结果为最小值
用stl map实现哈希的功能,比自己写哈希稍微慢了点,不过够用了。
本来尝试用stl hash_map实现,但是hash_map不是ansi c的标准,我用的编译器要加using namespace __gnu_cxx,并且wikioi的编译器不认,就没有用。
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<map> #include<algorithm> #include<vector> using namespace std; map<long long,int> cnt1,cnt2; int main() { int n; long long m; vector<int> da; scanf("%d%lld",&n,&m); for(int i=0; i<n; i++) { int tmp; scanf("%d",&tmp); da.push_back(tmp); } sort(da.begin(),da.end()); int l1 = da.size()/2; int l2 = da.size() - l1; for(int i=1; i<1<<l1; i++) { long long sum=0; int tc = 0; for(int j=i,k=0; j; j>>=1,k++) if(j&1) { sum += (long long)da[k]; tc++; } int now = cnt1[sum]; if(!now || tc<now) cnt1[sum] = tc; } for(int i=1; i<1<<l2; i++) { long long sum=0; int tc = 0; for(int j=i,k=l1; j; j>>=1,k++) if(j&1) { sum += (long long)da[k]; tc++; } int now = cnt2[sum]; if(!now || tc<now) cnt2[sum] = tc; } int ans = 50; map<long long,int>::iterator it; for(it=cnt1.begin(); it!= cnt1.end(); it++) { long long left = m-it->first; int tmp = it->second; if(left > 0) { int tmp2 = cnt2[left]; if(tmp2) { tmp += tmp2; ans = min(ans,tmp); } } } int r1 = cnt1[m]; int r2 = cnt2[m]; if(r1) ans = min(ans,r1); if(r2) ans = min(ans,r2); printf("%d\n",ans); }