poj 1787 Charlie's Change 多重背包给定容量使用最少(多)物品数

缘起

【1】中研究了 完全背包给定容量使用最少(多)物品数,本题是多重背包给定容量使用最少(多)物品数. poj 1787 Charlie’s Change

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
有1,5,10,25四种硬币,给定每种硬币的数量,给定要组合成的价值,
问刚好达到给定价值时用的硬币最多的情况。(毕竟大家喜欢多用掉一些零钱~)

【输入】
多样例. 每个样例占一行, 一行5个数字. P C1 C2 C3 C4, P是要付的钱, C1、C2、C3、C4分别是
1 5 10 25 的硬币的个数
1 <= P <= 10 000
0 <= Ci <= 10 000
输入以5个0结尾.

【输出】
对每个样例, 按照样例输出

【样例输入】
12 5 3 1 2
16 0 0 0 1
0 0 0 0 0

【样例输出】
Throw in 2 cents, 2 nickels, 0 dimes, and 0 quarters.
Charlie cannot buy coffee.

【限制】
1s

【1】中研究了完全背包要达到给定容量需要的最大(最小)物品数. 本题是多重背包, 那转换方法不就显然了吗? 就是多重背包转01背包+完全背包那一套搬过来就行了. 但是本题还要输出方案. 所以就还要维护一下路径了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//#include "stdafx.h"
#include <stdio.h>
#include <string.h>
//#define LOCAL
const int maxv = 10005, bytes = 5*sizeof(int);
int p, c[5] = {-1,1,5,10,25}, n[5], dp[maxv], path[maxv][5]; // dp[v]是仅仅考虑前几种硬币达到容量v的时候用的最多的硬币个数(当然,每种硬币的个数不能超过该种硬币自有的数量).path[v][1,2,3,4]是仅仅考虑前i种硬币时容量为v实现dp[v]的时候各种硬币的使用个数

void init() // 初始化, 非法状态都是-1
{
memset(dp,-1,sizeof(dp));
dp[0] = 0;
memset(path, -1, sizeof(path));
memset(path[0], 0, bytes);
}

void cp(int i,int cost) // 对cost耗费的物品做完全背包, 其实就是在【1】的基础上维护一下path
{
for (int v = cost; v<=p; ++v)
{
if (~dp[v-cost] && dp[v] < dp[v-cost]+1) // 要选第i种硬币, v-cost前提是合法状态, 即从合法的状态转移过来
{
dp[v] = dp[v-cost]+1;
memcpy(path[v], path[v-cost], bytes);
++path[v][i]; // 第i种硬币要选
}
}
}

void zo(int i, int cost, int k) // 对k件cost耗费的物品合并成为的一件物品做01背包
{
for (int v = p; v>=cost*k; --v)
{
if (~dp[v-k*cost] && dp[v]<dp[v-k*cost]+k) // v-k*cost 前提是合法状态
{
dp[v] = dp[v-k*cost]+k;
memcpy(path[v], path[v-k*cost], bytes);
path[v][i]+=k;
}
}
}

void mp(int i,int cost, int num) // 对num个cost耗费的物品(每个物品的收益是1)做多重背包
{
if (cost*num>=p)
{
cp(i,cost);
}
else
{
int k = 1;
while(k<num)
{
zo(i,cost,k);
num-=k;
k<<=1;
}
zo(i,cost,num);
}
}

int main()
{
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
// freopen("d:\\my.out", "w", stdout);
#endif
while(scanf("%d%d%d%d%d", &p, n+1,n+2,n+3,n+4), p||n[1]||n[2]||n[3]||n[4])
{
init();
for (int i = 1;i<=4;i++)
{
mp(i,c[i], n[i]);
}
~dp[p]?printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n",path[p][1],path[p][2],path[p][3],path[p][4]):puts("Charlie cannot buy coffee.");
}
return 0;
}

ac情况

Status Accepted
Time 454ms
Memory 344kB
Length 1629
Lang C++
Submitted 2019-10-27 17:38:20
Shared
RemoteRunId 20995900

参考

【1】https://yfsyfs.github.io/2019/10/27/joyoi-%E9%82%AE%E7%A5%A8%E9%97%AE%E9%A2%98-%E5%AE%8C%E5%85%A8%E8%83%8C%E5%8C%85/