动态规划问题,以问题中各个子问题的解可以递推(既解互有递推关系)得来为特点,代码简介且高效。重点为找出递推关系。
通过下题展开讲解。
问题描述
:现在拥有极多种类的硬币(下列代码中假设了1 5 10 25 50五种,实际可以修改为更多种类)无限个,给定一个金额,要求输出:1.达到此金额的最小硬币数 2.最小硬币数的组成方案。
分析:
第一个问题
给定了硬币种类(面值),要求推导任意金额的最小组成硬币数。我们可以试着去列一下子问题的解
单单看面值为1的硬币时
金额:0 1 2 3 4 5 6 7 8 9 10
数目:0 1 2 3 4 5 6 7 8 9 10
可以朴素的得出min[i]=i;
如果有面值为5的硬币进行参与
金额:0 1 2 3 4 5 6 7 8 9 10
数目:0 1 2 3 4 1 2 3 4 5 2
观察发现加入硬币5后会在只考虑硬币1得到的答案基础上有所变化,当i达到5也就金额达到5时,我们可以用一个面值为5的硬币来替代前4枚硬币+一枚面值为1的硬币的做法。简单来说就是在我们有1枚五块和五个一块时选择了前者
可以得到递推关系 dp[i]=min(dp[i],min[i-5]+1)
可以发现实际上我们添加一个新硬币进去我们就会得到这样的关系
dp[i]=min(dp[i],dp[i-新硬币数额]+1)
好的,现在的到了这样的一个关系之后我们就可以进行打表
操作,也就如下面代码的slove函数一样。
第二个问题:
我们如何找到所谓的组成方案呢。不妨想一下,如果知道了一个金额的最小组成硬币数,那么我们也一定能知道在组成它的这些硬币中面值最大的是哪一个。
以15为例,我们知道最大的面值为10,那么用15-10再去找,对应的就是5,5-5=0,最终方案就是10 5
为了实现这一操作,我们需要一个数组maps来存放每一个金额对应的最小硬币数组合中的最大面值硬币
。我们记金额为n,每一次都让n = n-maps[n]
那么就可以在金额不断缩小的时候(就像刚才15到5再到0)保证每次都减去了此金额对应最小硬币数组合的最大硬币面值
。
易得出如下代码输出对应组合:
while(n)
{
cout<<maps[n]<<" ";
n=n-maps[n];
}
我们已经找到两个问题所对应的推导关系,这意味着DP问题已经接到达尾声,代码简洁是因为他有递归的影子,效率高是因为他不用反复运算,对已经有的数据运用效率高。
最终代码如下。
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <cstdio>
#include <string>
#include <cstring>
#include <string.h>
#include <vector>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
using namespace std;
int type[5]={1,5,10,25,50};
/*五种硬币*/
int dp[1000];/*假设这个最大数额是1000*/
int maps[1000]={0};
void slove()
{
for(int i=0;i<5;i++)
{
for(int j=type[i];j<=1000;j++)
{
if(dp[j]>dp[j-type[i]]+1)
{
dp[j]=min(dp[j],dp[j-type[i]]+1);
maps[j]=type[i];
}
}
}
}
void show_maps(int n)
{
while(n)
{
cout<<maps[n]<<" ";
n=n-maps[n];
}
cout<<endl;
}
int main()
{
int t;
for(int i=0;i<=1000;i++)
{
dp[i]=INT_MAX;
}
dp[0]=0;
slove();
for(cin>>t;t;t--)
{
int n;
cin>>n;
cout<<dp[n]<<endl;
show_maps(n);
}
return 0;
}
可见动态规划问题的根本就是搞出递推关系找出所有解。
拓展问题:
求出给定金额的所有硬币组成方案数(求的仅仅为一个值而不是输出所有方案)