题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2844
Coins
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 10070 Accepted Submission(s): 4015
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
Source
一开始想到HDU2082,于是暴力3个for,果断超时。
后来看了背包九讲才知道有个二进制优化的方法:把多重背包问题转化为01背包,将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。
因为1,2,4,8,16,32......这个数列里的数,通过加法能组成所有的自然数。
具体看代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f3f
int val[110], c[110];
int dp[100010]; //dp数组用来储存花费 i 空间时最多能获得多少价值
int n, m;
void zreo(int cost) //01背包
{
for (int i = m; i >= cost; i--)
dp[i] = max(dp[i], dp[i - cost] + cost);
}
void com(int cost) //完全背包
{
for (int i = cost; i <= m; i++)
dp[i] = max(dp[i], dp[i - cost] + cost);
}
void f(int val, int c)
{
if (val*c >= m){ //当硬币价值*数量比m大时,可以把此硬币当做无限使用,所以用完全背包
com(val);
return;
}
int k = 1;
while (k <= c) //否则,拆分硬币数量,把拆分的数量*价值,当做一种新的硬币使用
{
zreo(val*k); //01背包
c -= k;
k <<= 1;
}
zreo(val*c);
}
int main()
{
while (scanf("%d%d", &n, &m) != EOF && n && m)
{
for (int i = 1; i <= n; i++)
scanf("%d", &val[i]);
for (int i = 1; i <= n; i++)
scanf("%d", &c[i]);
for (int i = 0; i <= m; i++)
dp[i] = -INF;
dp[0] = 0;
for (int i = 0; i <= n; i++) //枚举每一种硬币
f(val[i], c[i]);
int ans = 0;
for (int i = 1; i <= m; i++)
if (dp[i] >= 0) //只要dp储存过价值 i,就说明价值 i 可以用硬币组成
ans++;
printf("%d\n", ans);
}
}