POJ - 1742

People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch. 
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins. 

Input

The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

Sample Output

8
4

题意:有n种硬币,面值、数量分别为AI,CI, 求这些硬币能凑出1-m面值的方案数。

此题重点卡时间,需要优化。

法一 朴素暴力 时间复杂度(O(n*m*c))  超时警告

#include<iostream>
#include<cstring>

using namespace std;
int a[105],c[105];
bool dp[105][100005];
int main()
{
    int n,m,ans;
    while(cin>>n>>m&&n&&m){
       ans=0;
       memset(dp,false,sizeof(dp));
       for(int i=1; i<=n; i++){
          cin>>a[i];//价值
       }
       for(int i=1; i<=n; i++){
          cin>>c[i];//数量
       }
       for(int i=1; i<=n; i++){//边界处理,只选用第i种硬币单独组成
           for(int j=1; j<=c[i]; j++){
              if(j*a[i]<=m){
                 dp[i][j*a[i]]=true;
              }
              else break;
           }
       }
       for(int i=2; i<=n; i++){
           for(int j=1; j<=m; j++){
              if(dp[i-1][j]==true)
                  dp[i][j]=dp[i-1][j];//不选
               for(int k=1;k<=c[i]; k++){//选
                    if(j-k*a[i]>=1&&dp[i-1][j-k*a[i]]==true){
                        dp[i][j]=true;
                    }
               }
           }
       }
       for(int i=1; i<=m; i++){
           if(dp[n][i]==true){
              ans++;
           }
       }
       cout<<ans<<endl;
    }
    return 0;
}

 

法二 多重背包问题        时间复杂度\left ( n*log\sum Ci \right )超时警告 

利用二进制拆分物品

#include<iostream>
#include<cstring>
#include<cstdio>

using namespace std;
int a[105],c[105];
int dp[100005];
int n,m,ans;

void complete_pack(int v){
    for(int i=v; i<=m; i++){
       dp[i] |= dp[i-v];
    }
}

void zeroone_pack(int v){
    for(int i=m; i>=v; i--){
       dp[i] |= dp[i-v];
    }
}
int main()
{

    while(scanf("%d%d",&n,&m)){
       if(n==0&&m==0) break;
       ans=0;
       memset(dp,0,sizeof(dp));
       for(int i=1; i<=n; i++){//价值
          scanf("%d",&a[i]);
       }
       for(int i=1; i<=n; i++){
          scanf("%d",&c[i]);//数量
       }
       dp[0]=1;//
       for(int i=1;i<=n; i++){
          if(c[i]>0){
              if(c[i]*a[i]>=m){//完全背包
                 complete_pack(a[i]);
              }
              else{//二进制拆分
                  int j=1;
                  while(j<=c[i]){
                     zeroone_pack(a[i]*j);//01背包
                     c[i]-=j;
                     j<<=1;
                  }
                  if(c[i]>0){
                      zeroone_pack(a[i]*c[i]);
                  }
              }
          }
       }
       for(int i=1; i<=m; i++){
              ans+=dp[i];
       }
       printf("%d\n",ans);
    }
    return 0;
}

 

 

法三:用bitset+多重背包二进制优化  可以通过   n*log\left ( \sum Ci /32 \right )

 

如有

价值1的硬币5枚

    初始 00001 (从右到左,低位从0开始)

   j=1 ,整体向左移1位  00010 ,  00001 | 00010 =00011 (表示0,1的价值)

  j=2  整体向左移两位  01100,   01100 | 00011= 01111 (表示0,1,2,3的价值,花掉了1+2=3个硬币)

所以价值j就需要整体向左移j位,再跟前一个状态或操作,再加上多重背包二进制的优化。

#include<iostream>
#include<cstring>
#include<cmath>
#include<bitset>
using namespace std;

const int N=105;
const int M=100005;
int a[N];
bitset<M>b;
int main()
{
    int n,m,ans,x;
    while(cin>>n>>m&&n&&m){
       for(int i=1; i<=n;i++){
          cin>>a[i];//价值
       }
       b.reset();//全部置0
       b.set(0);//将下标0置1  表示价值0是可以拼成的。
       for(int i=1; i<=n; i++){
          cin>>x;
          for(int j=1; j<=x; x-=j, j<<=1){
              b |= b<<(a[i]*j); //重点 花掉价值多少就需要整体往左移多少位,并跟原状态做与操作
          }
          b |= b<<(a[i]*x);
       }
       ans=0;
       for(int i=1; i<=m; i++){
          if(b[i]) ans++;
       }
       cout<<ans<<endl;
    }
    return 0;
}

 

法四:动态规划+贪心思想  时间复杂度    O(N*M) 可以通过

used[j]表示 凑成j最少用第i种硬币多少枚、

f[i]表示价值i能否凑成,0表示不能,1表示能。

那么凑成j时,有以下两种情况:

1、前i-1种硬币已经能凑成j了,即第i种硬币没有使用,此时  used[i]=0,f[j]=1

2、前i-1种硬币不能凑成j, f[j]=0, 假如 f[j-a[i]]能凑成(f[j-a[i]]=1)并且第i种硬币还没有使用完(used[j-a[i]]<c[i]),那么再用一枚i硬币就可以凑成j。

  即更新f[j]=1,used[j]=used[j-a[i]]+1;

根据贪心思想,应该尽量使用情况一。

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;

const int N=105;
const int M=100005;
int a[N],c[N],used[M],f[M];
int main()
{
    int n,m,ans,x;
    while(cin>>n>>m&&n&&m){
       for(int i=1; i<=n;i++){
          cin>>a[i];//价值
       }
       for(int i=1; i<=n; i++){//数量
          cin>>c[i];
       }
       memset(f,0,sizeof(f));
       f[0]=1;
       for(int i=1; i<=n; i++){
          memset(used,0,sizeof(used));
          for(int j=a[i]; j<=m; j++){
             if(!f[j]&&f[j-a[i]]&&used[j-a[i]]<c[i]){//情况二
                 f[j]=1;//更新
                 used[j]=used[j-a[i]]+1;//更新
             }
          }
       }
       ans=0;
       for(int i=1; i<=m; i++){
           ans+=f[i];
       }
       cout<<ans<<endl;
    }
    return 0;
}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值