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.
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);
}
}