2144 砝码称重 2(meet in the middle)

2144 砝码称重 2

http://codevs.cn/problem/2144/

题目描述 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);
}

 

posted @ 2016-12-27 21:14  TRTTG  阅读(504)  评论(0编辑  收藏  举报