题目链接: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
32 39 50
1 2 1
3 70
1 2 3
1 2 3
3 70
40 50 60
0 0 0
3 70
50 60 90
1 1 1
3 7
1 2 3
2 1 1
3 4
3 101 100
1 1 0
代码:
#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原创,但可以转载,因为我们是兄弟。