2144 砝码称重 2(meet in the middle)
2144 砝码称重 2
题目描述 Description
有n个砝码,现在要称一个质量为m的物体,请问最少需要挑出几个砝码来称?
注意一个砝码最多只能挑一次
输入描述 Input Description
第一行两个整数n和m,接下来n行每行一个整数表示每个砝码的重量。
输出描述 Output Description
输出选择的砝码的总数k,你的程序必须使得k尽量的小。
样例输入 Sample Input
3 10
5
9
1
样例输出 Sample Output
2
数据范围及提示 Data Size & Hint
1<=n<=30,1<=m<=2^31,1<=每个砝码的质量<=2^30
方法一:深搜,用后缀和优化
读入数据用读入优化
剪枝1:如果当前使用的砝码数>=当前最优解,return
剪枝2:深搜之前按从大到小排序,如果当前重量+当前砝码的后缀和<m ,return
剪枝3:如果当前重量+当前砝码重量>m ,换下一个砝码,注意不能return
因为砝码从大到小排序,后面的后缀和一定小于前面的,所以如果当前重量+当前砝码的后缀和<m,那么后面的更小,所以直接return
而如果当前重量+当前砝码重量>m ,下一个砝码的质量更小,所以有可能产生答案,所以不能return
总耗时:201ms
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; int n,m,ans; long long a[31],suf[31]; void dfs(int now,int use,long long wei)//now当前第几个砝码,引入这个变量避免了很多重复搜索;use当前使用砝码总数;wei当前使用砝码总重量 { if(use>=ans) return;//当前砝码使用量>=当前最优解 if(wei==m) ans=min(ans,use); for(int i=now+1;i<=n;i++) { if(wei+suf[i]<m) return;//当前重量+当前砝码后缀和<目标质量 if(wei+a[i]>m) continue;//当前重量+当前砝码重量>目标重量 dfs(i,use+1,wei+a[i]); } } long long init()//读入优化 { long long x=0;char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();} return x; } bool cmp(long long p,long long q) {return p>q;} int main() { n=init();m=init(); for(int i=1;i<=n;i++) a[i]=init(); sort(a+1,a+n+1,cmp);//从大到小排序 for(int i=n;i;i--) suf[i]=suf[i+1]+a[i];//后缀和 ans=n; dfs(0,0,0); printf("%d",ans); }
方法二:双向搜索
每次只深搜前一半,深搜完了在深搜后一半,如果在搜索后一般的过程中,发现结果有与前一半的搜索结果相加等于m的,那就用这两部分的步数和相加更新答案
不用hash 51ms
#include<map> #include<iostream> #include<algorithm> using namespace std; int n, mass, ans(666), f[233]; map<int, int>m;//能称出的质量→需要的砝码 void dfs(int step, int last, int sum, bool k) { int r(n);//右边界 if (k)//如果是前半段 m[sum] = step, r /= 2;//记录搜到的所有解 else//后半段 if (m.find(mass - sum) != m.end())//如果能跟前半段的结果组成目标质量 ans = min(ans, step + m[mass - sum]);//更新答案 for (int i(last); i < r; ++i) //生成全组合 dfs(step + 1, i + 1, sum + f[i], k); } int main() { cin >> n >> mass; for (int i(0); i < n; ++i) cin >> f[i]; dfs(0, 0, 0, true);//先搜前半段 dfs(0, n / 2, 0, false);//再搜后半段 cout << ans << endl; return 0; }
方法三:方法二的原理+hash
用hash 46ms,应该是数据比较水,不然hash应该更快
#include<cstdio> #include<iostream> #include<algorithm> #define mod1 1009 #define mod2 10000007 using namespace std; int n,m,ans,cnt; long long a[31]; int head[1009]; struct node { int next,w,to;//w表示hash值为to时需要的步骤数 }e[5000001]; int get_hash1(long long x)//双模哈希 { return x%mod1; } int get_hash2(long long x) { return x%mod2; } long long init()//读入优化 { long long x=0;char c=getchar(); while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') {x=x*10+c-'0';c=getchar();} return x; } void add(int u,int v,int t) { cnt++; e[cnt].to=v; e[cnt].w=t; e[cnt].next=head[u]; head[u]=cnt; } int find(int s,int z) { for(int i=head[s];i;i=e[i].next) if(e[i].to==z) return e[i].w; return -1; } void dfs(int now,int use,long long wei,bool judge) //now当前第几个砝码,引入这个变量避免了很多重复搜索;use当前使用砝码总数;wei当前使用砝码总重量;judge=0表示搜索前一半,=1搜索后一半 { int r=n;//右边界 if(!judge) { add(get_hash1(wei),get_hash2(wei),use); r/=2;//只搜前一半 } else { int h1=get_hash1(m-wei),h2=get_hash2(m-wei);//hash int p=find(h1,h2); if(p>=0) ans=min(ans,p+use); } for(int i=now+1;i<=r;i++) if(wei+a[i]<=m) dfs(i,use+1,wei+a[i],judge); } int main() { n=init();m=init(); ans=n; for(int i=1;i<=n;i++) a[i]=init(); dfs(0,0,0,0);//搜索前一半 dfs(n/2,0,0,1);//搜索后一半 printf("%d",ans); }