最少硬币问题Time Limit: 1000 ms Memory Limit: 65536 KiB Problem Description 设有n种不同面值的硬币,各硬币的面值存于数组T[1:n]中。现要用这些面值的硬币来找钱。可以使用的各种面值的硬币个数存于数组Coins[1:n]中。 Input 输入数据第一行中只有1个整数给出n的值,第2行起每行2个数,分别是T[j]和Coins[j]。最后1行是要找的钱数m。 Output 输出数据只有一个整数,表示计算出的最少硬币数。问题无解时输出-1。 Sample Input Sample Output Hint Source |
我们再回想一下实现动态规划的前提:
- 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关;
- 有重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)。
在做这道题之前,我们先来看一个样例:
问题描述
假设有 1 元,3 元,5 元的硬币若干(无限),现在需要凑出 11 元,问如何组合才能使硬币的数量最少?
问题分析
乍看之下,我们简单的运用一下心算就能解出需要 2 个 5 元和 1 个 1 元的解。当然这里只是列出了这个问题比较简单的情况。当硬币的币制或者种类变化,并且需要凑出的总价值变大时,就很难靠简单的计算得出结论了。贪心算法可以在一定的程度上得出较优解,但不是每次都能得出最优解。
这里运用动态规划的思路解决该问题。按照一般思路,我们先从最基本的情况来一步一步地推导。
我们先假设一个函数 d(i) 来表示需要凑出 i 的总价值需要的最少硬币数量。
- 当 i = 0 时,很显然我们可以知道 d(0) = 0。因为不要凑钱了嘛,当然也不需要任何硬币了。注意这是很重要的一步,其后所有的结果都从这一步延伸开来。
- 当 i = 1 时,因为我们有 1 元的硬币,所以直接在第 1 步的基础上,加上 1 个 1 元硬币,得出 d(1) = 1。
- 当 i = 2 时,因为我们并没有 2 元的硬币,所以只能拿 1 元的硬币来凑。在第 2 步的基础上,加上 1 个 1 元硬币,得出 d(2) = 2。
- 当 i = 3 时,我们可以在第 3 步的基础上加上 1 个 1 元硬币,得到 3 这个结果。但其实我们有 3 元硬币,所以这一步的最优结果不是建立在第 3 步的结果上得来的,而是应该建立在第 1 步上,加上 1 个 3 元硬币,得到 d(3) = 1。
- ...
接着就不再举例了,我们来分析一下。可以看出,除了第 1 步这个看似基本的公理外,
其他往后的结果都是建立在它之前得到的某一步的最优解上,加上 1 个硬币得到。
得出:d(i) = d(j) + 1
这里 j < i。通俗地讲,我们需要凑出 i 元,就在凑出 j 的结果上再加上某一个硬币就行了。
那这里我们加上的是哪个硬币呢。嗯,其实很简单,把每个硬币试一下就行了:
- 假设最后加上的是 1 元硬币,那 d(i) = d(j) + 1 = d(i - 1) + 1。
- 假设最后加上的是 3 元硬币,那 d(i) = d(j) + 1 = d(i - 3) + 1。
- 假设最后加上的是 5 元硬币,那 d(i) = d(j) + 1 = d(i - 5) + 1。
我们分别计算出 d(i - 1) + 1,d(i - 3) + 1,d(i - 5) + 1 的值,取其中的最小值,即为最优解,也就是 d(i)。
最后公式:
通过上面的栗子,我们也就理解这道题怎么做了,加油啦!!
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f;
int coin[11];
int num[11];
int dp[20005];
int main()
{
int n,m;
scanf("%d",&n);
for(int i=0; i<n; i++)
{
scanf("%d%d",&coin[i],&num[i]);
}
scanf("%d",&m);
for(int i=1; i<=m; i++)
{
dp[i]=inf;
}
dp[0]=0;//特别重要,别忘了
for(int i=0; i<n; i++)//一种一种的来
{
for(int j=1; j<=num[i]; j++)
{
for(int k=m; k>=coin[i]; k--)
{
dp[k]=min(dp[k],dp[k-coin[i]]+1);
//printf("%d %d\n",k,dp[k]);//可以跑一下这个,看看代码是怎么执行的
}
}
}
printf("%d\n",dp[m]<m?dp[m]:-1);
return 0;
}