HDU_2844 Coins(DP)

Coins

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7938    Accepted Submission(s): 3240


Problem Description
Whuacmers use coins.They have coins of value A1,A2,A3...An Silverland dollar. One day Hibix opened purse 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种物品和体积为V的背包,物品i的体积为v[i],价值为w[i],数目为n[i],求怎样装使背包内物品价值最大。
对于这道题来说,这个多重背包的v与w是相等的 ,我们把1~m之间的每一个钱数当做一个背包,我们只要求在1~m之间有多少个背包满足最大价值就好。
附上两种不同的解法:
方法1:O(nm)。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <limits.h>
#define MAX_A 100020
#define MAX_N 110
using namespace std;

//dp数组存储加和得到j时第i种能剩多少个
int dp[MAX_A];
int n,m,num;
int a[MAX_N];//价值
int c[MAX_N];//数目
int main()
{
    while( scanf("%d%d",&n,&m) != EOF )
    {
        if( n == 0 && m == 0 )
            return 0;
        for( int i = 0; i < n; i++ )
            scanf("%d",&a[i]);
        for( int i = 0; i < n; i++ )
            scanf("%d",&c[i]);
        //将初始值设为-1表示不能取到j
        memset(dp,-1,sizeof(dp));
        dp[0]=0;num=0;
        for( int i = 0; i < n; i++ )
        {
            for( int j = 0; j <= m; j++ )
            {
                //j已经可以取到
                if( dp[j] >= 0 )
                    dp[j]=c[i];
                //两种情况j取不到
                else if( j < a[i] || dp[j-a[i]] <= 0 )
                    dp[j]=-1;
                //j可以取到,更新剩余个数
                else
                    dp[j]=dp[j-a[i]]-1;
            }
        }
        //有一种方法类似,但是要稍微快一些
        //dp数组存储加和得到j时第i种用多少个
        /*
        int vis[MAX_A];
        memset(dp,0,sizeof(dp));
        memset(vis,0,sizeof(vis));
        for( int i = 0; i < n; i++ )
        {
            memset(dp,0,sizeof(dp));
            for( int j = a[i]; j <= m; j++ )
            {
                if( vis[j-a[i] && !vis[j] )
                {
                    if( dp[j-a[i]] < c[i] )
                    {
                        vis[j]=1;
                        num++;
                        dp[j]=dp[j-a[i]]+1;
                    }
                }
            }
        }
        */
        for( int i = 1; i <= m; i++ )
        {
            //剩余个数>=0表示最终能取到
            if( dp[i] >= 0 )
                num++;
        }
        printf("%d\n",num);
    }
    return 0;
}
方法2:O(n∑logci)。我们可以把多重背包转换为我们学过的01背包或者完全背包。把第i种物品换成n[i]件01背包中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是O(V ∑n[i])。但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略取0..n[i]件均能等价于取若干件代换以后的物品
多重背包代码精华:
void multiply(int cost,int n){
    if(cost*n>=v) complete(cost);
    else{
        int k=1;
        while(k<n){
        bag01(cost*k);
        n-=k;
        k*=2;
        }
        bag01(n*cost);
    }
}

//dp数组存储背包为j时的总价值最大值
int main()
{
    while( scanf("%d%d",&n,&m) && (n||m) )
    {
        for( int i = 0; i < n; i++ )
            scanf("%d",&a[i]);
        for( int i = 0; i < n; i++ )
            scanf("%d",&c[i]);
        //初始值设为0
        memset(dp,0,sizeof(dp));num=0;
        for( int i = 0; i < n; i++ )
        {
            //如果物品i取不完,化为完全背包
            if( a[i]*c[i] > m )
            {
                for( int j = a[i]; j <= m; j++ )
                    dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
            }
            else
            {
                //二进制优化,将物品i分为1,2,4...(和等于i的总个数),转化为01背包
                int k;
                int amount=c[i];
                for( k = 1; k < amount; amount-=k,k*=2 )
                {
                    for( int j = m; j >= k*a[i]; j-- )
                        dp[j]=max(dp[j],dp[j-k*a[i]]+k*a[i]);
                }
                for( int j = m; j >= amount*a[i]; j-- )
                    dp[j]=max(dp[j],dp[j-amount*a[i]]+amount*a[i]);
            }
        }
        for( int i = 1; i <= m; i++ )
        {
            if( i == dp[i] )
                num++;
        }
        printf("%d\n",num);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值