题目链接:http://poj.org/problem?id=1787
题目大意:给定一个数p,要求用四种币值为1,5,10,25的硬币拼成p,并且硬币数要最多,如果无解输出"Charlie cannot buy coffee.",1<=p<=1万,1<=硬币数量<=1万
解题思路:看到题目的第一想法是多重背包,把币值和p看成容量,硬币数看成价值,问题就转换为最容量为p的背包中价值最大为多少?刚入手用二进制处理将多重背包转换为01背包然后求解,速度奇慢,跑了969ms,优化后变成675ms。复杂度度为O(p*sum(log(num[i]))。AC之后看别人的解题报告,惊讶地发现他们都用完全背包写这题,复杂仅为(4*p),速度快了5倍多。
两种背包的状态转移方程都一样:dp[j] = max(dp[j],dp[j-mon[i]+val[i])(多重背包解法val[i]>=1,1<=i<=tot,完全背包解法val[i]=1,1<=i<=4)
测试数据:
12 5 3 1 2
代码:
//完全背包 #include <stdio.h> #include <string.h> #define MAX 20000 int p,dp[MAX],path[MAX]; int num[50],mon[50],used[MAX]; int main() { int i,j,k,tp; mon[1] = 1,mon[2] = 5; mon[3] = 10,mon[4] = 25; while (scanf("%d",&p)) { for (i = 1; i <= 4; ++i) scanf("%d",&num[i]); if (p == 0) break; memset(dp,0,sizeof(dp)); memset(path,0,sizeof(path)); path[0] = -1,dp[0] = 1; for (i = 1; i <= 4; ++i) { memset(used,0,sizeof(used)); for (j = mon[i]; j <= p; ++j) if (dp[j-mon[i]] + 1 > dp[j] && dp[j-mon[i]] && used[j-mon[i]] + 1 <= num[i]) { dp[j] = dp[j-mon[i]] + 1; used[j] = used[j-mon[i]] + 1; path[j] = j - mon[i]; } } if (dp[p] != 0) { memset(num,0,sizeof(num)); while (path[p] != -1) { num[p-path[p]]++; p = path[p]; } printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n",num[1],num[5],num[10],num[25]); } else printf("Charlie cannot buy coffee.\n"); } return 0; }
//多重背包 #include <stdio.h> #include <string.h> #define MAX 20000 #define INF 1000000000 #define max(a,b) (a)>(b)?(a):(b) int num[5],mon[5],in[MAX]; int tot,cost[MAX],val[MAX]; int p,ans,dp[100][MAX],path[100][MAX]; void Solve_1A() { int i,j,k; tot = 0; for (i = 1; i <= 4; ++i) { for (j = 0; (1<<j) <= num[i]; ++j) { k = 1<<j; num[i] -= k; cost[++tot] = k * mon[i]; val[tot] = k; in[tot] = i; } if (num[i]) { in[++tot] = i; val[tot] = num[i]; cost[tot] = num[i] * mon[i]; } num[i] = 0; //最后拿来存放答案,老师说要懂得回收利用 } for (i = 0; i <= tot; ++i) for (j = 0; j <= p; ++j) path[i][j] = 0,dp[i][j] = -INF; dp[0][0] = 0; for (i = 1; i <= tot; ++i) { for (j = p; j >= 0; --j) if (j >= cost[i]) { if (dp[i-1][j-cost[i]] != -INF && dp[i-1][j-cost[i]] + val[i] > dp[i-1][j]) dp[i][j] = dp[i-1][j-cost[i]] + val[i],path[i][j] = 1; else dp[i][j] = dp[i-1][j]; } else dp[i][j] = dp[i-1][j]; } if (dp[tot][p] != -INF) { j = tot,k = p; while (j > 0) { if (path[j][k]) num[in[j]] += val[j],k -= cost[j]; j--; } } } int main() { int i,j,k; mon[1] = 1,mon[2] = 5; mon[3] = 10,mon[4] = 25; while (scanf("%d",&p)) { for (i = 1; i <= 4; ++i) scanf("%d",&num[i]); if (p == 0) break; Solve_1A(); if (dp[tot][p] == -INF) printf("Charlie cannot buy coffee.\n"); else printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.\n",num[1],num[2],num[3],num[4]); } return 0; }