题目链接:http://poj.org/problem?id=3260
题目大意:农民John去购物,市面流行的硬币有n种,每种面值mon[i],John有num[i]个,现在他要到煤老板的店里去买m元的东西,大家都知道煤老板不差钱,每种硬币都有无数个,煤老板找钱的话都可以找最少数量的硬币给John,现在John想要自己出的硬币数和老板找的硬币数最小,如果找不到符合情况的解,输出-1.
解题思路:Usaco的题目质量我觉得还不错,做了几道算法应用题,难度中等,挺适合这个阶段的自己。本题需要用多重背包和完全背包解,算比较综合的题目。因为每次找钱的数量是一定的,我们可以提前用完全背包算出j元钱需要的最少硬币数minn[j]。将找钱的最少硬币预处理完之后,就可以开始进行多重背包求解,因为数据量比较大必须用二进制先进行处理,处理成数量最多为1500左右的物品,然后用01背包求解。当John给煤老板超过m元钱的时候有三种情况:刚好是m元,那就可以用这个更新答案,如果大于m元并且能找的断,那也用这次计算的硬币数更新答案,如果找不断,那么需要一直给煤老板钱,直到老板找得了钱或者你没钱。其实给的钱会有个临界值,我设为m + 10000了。
状态转移方程:if (j >= m && dp[j-cost[i]]+minn[j-m]+val[i] < dp[j]) dp[j] = dp[j-cost[i]]+minn[j-m]+val[i];
else dp[j] = min(dp[j],dp[j-cost[i]]+val[i]);
复杂度O((V+10000)*sum(log(num[i]))
测试数据:
3 60
代码:
#include <stdio.h> #include <string.h> #define MAX 20000 #define INF 100000000 #define min(a,b) (a)<(b)?(a):(b) int tot,cost[MAX],val[MAX]; int minn[MAX],dp[MAX],maxval; int n,m,ans,mon[MAX],num[MAX]; void Initial() { int i,j,k,tpk; for (i = 0; i <= m + maxval; ++i) dp[i] = minn[i] = INF; minn[0] = dp[0] = 0; //煤老板找钱的最少数量,硬币无限 for (i = 1; i <= n; ++i) for (j = mon[i]; j <= m + maxval; ++j) minn[j] = min(minn[j],minn[j-mon[i]]+1); //将各种物品,二进制处理若干件物品 for (tot = 0,i = 1; i <= n; ++i) { for (k = 0; (1<<k) <= num[i]; ++k) { num[i] -= (1<<k); cost[++tot] = mon[i] * (1<<k); val[tot] = (1<<k); } if (num[i]) cost[++tot] = mon[i] * num[i],val[tot] = num[i]; } } int main() { int i,j,k; while (scanf("%d%d",&n,&m) != EOF) { maxval = 10000; for (i = 1; i <= n; ++i) scanf("%d",&mon[i]); for (i = 1; i <= n; ++i) scanf("%d",&num[i]); Initial(),ans = INF; for (i = 1; i <= tot; ++i) for (j = m + maxval; j >= cost[i]; --j) { if (j >= m && dp[j-cost[i]]+minn[j-m]+val[i] < dp[j]) { dp[j] = dp[j-cost[i]]+minn[j-m]+val[i]; ans = min(dp[j],ans); } else dp[j] = min(dp[j],dp[j-cost[i]]+val[i]); } if (ans >= INF) printf("-1\n"); else printf("%d\n",ans); } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。