基础DP

内容参考书籍《算法竞赛入门到进阶》

  DP与分治法的区别:

  (1)分治法是把问题分成独立的子问题,各个子问题能独立解决,一个子问题内部的计算不需要其他子问题的数据,例如归并排序。

  (2)DP的子问题之间是相关的,前面子问题的解决结果被后面的子问题使用。

  (DP比分治复杂得多)

  求解DP问题有3步,即定义状态、状态转移、算法实现。DP的核心是状态、状态转移方程。用状态转移方程求解状态,状态往往就是问题的解。

下面从简单的硬币问题开始引出动态规划:

 最少硬币问题:有n种硬币,面值分别为v1 v2 v3 ...vn ,数量无限。输入非负整数s,选用硬币使其和为s,要求输出最少的硬币组合。

定义一个数组Min[MONEY],其中Min[i]是金额i对应的最少硬币数量。对于输入的某个金额i,只要查Min[i]即可。

那么如何计算Min[i]呢?下面我们以面值(1、5、10、25、50)为例讲解递推过程。

首先我们类比一下斐波那契数列,要输出斐波那契的某一项时,我们可以将斐波那契数列前n项全部算出来,对于第i项直接查数组即可。

我们知道,斐波那契数列的第一项是1,第二项是1,往后的所有项都是前两项的和即F[n]=F[n-1]+F[n-2],于是我们可以通过一个循环一项一项地计算到第n项。

同样,对于这道题我们知道Min[0]=0,假设现在我只考虑面值为1的硬币,那么Min[1]=1,Min[2]=2,即:Min[i]=Min[i-1]+1

那么如果我们只有面值为1的硬币,对于金额为i的解我们便可以通过上述方程通过一个循环解出来。

接下来我们增加面值为5的硬币,我们知道Min[5]=1,因为金额为5只需要一枚面值为5的硬币即可,而Min[6]=2,因为只需要一枚5和一枚1,Min[7]=3

我们发现金额大于5的答案在加入了金额为5之后将更新为Min[i]=min(Min[i],Min[i-5]+1)。接下来处理其他面值即可。

值得注意的是,在DP中,不仅要求最优解数量,往往还要输出最优解本身。

在上诉问题中,需要增加一个记录表Min_path[i],记录金额i需要的最后一枚硬币。例如Min_path[6]=5,表示最后一个硬币是5,而Min_path[6-5]=1,表示接下来一枚是1,最后Min_path[0]=0,输出即可。

那如果我们要输出所有方案数呢?可以通过类似上述思路求解,首先定义一个数组dp[num],dp[i]表示金额i对应的组合方案数。

假设我们现在也只考虑面值为1的硬币,显然dp[0]=1,一个硬币也不用也是一种方案,这方便我们接下来的处理,dp[1]=1,dp[2]=1...由于只有一个硬币所以方案数都是1。

现在增加面值为5的硬币,那么显然dp[5]=2,dp[6]=2,dp[10]=3,由此我们推导出方程为dp[i]=dp[i]+dp[i-5],同理增加剩下的面值即可。

 然而我们又遇到了一个新的问题,假如题目限制了硬币的数量,这时我们就需要重新定义状态为dp[i][j],其中i表示金额,j表示硬币数,dp[i][j]表示用j个硬币实现金额i的方案数。

我们定义type[5]={1,5,10,25,50},初始化dp[0][0]=1,其余为0.

先考虑只有面值为1的硬币,dp[1][1]=1,因为硬币数量+1,方案数为前一个状态方案数,即:dp[1][1]=dp[0][0]=1,但要考虑到原有方案数,故修正关系为dp[1][1]=dp[1][1]+dp[0][0].

把上述方程改写成dp[1][1]=dp[1][1]+dp[1-type[0]][1-1].

增加面值为5的硬币有dp[i][j]=dp[i][j]+dp[i-type[1]][j-1]

继续增加则有此关系:dp[i][j]=dp[i][j]+dp[i-type[k]][j-1],k=2,3,4

hdu2069:http://acm.hdu.edu.cn/showproblem.php?pid=2069

代码如下:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int COIN = 101;//题目要求不超过100个硬币
 4 const int MONEY = 251;//题目给定的钱数不超过250
 5 int dp[MONEY][COIN];//初始化dp转移矩阵
 6 int type[5] = {1,5,10,25,50};//5种面值
 7 void solve()        //dp
 8 {
 9     dp[0][0] = 1;
10     for (int i = 0; i < 5; ++i)//依次增加5种面值,更新dp
11         for (int j = 1; j < COIN; ++j)//遍历硬币数
12             for (int k = type[i]; k < MONEY; ++k)//从该面值开始往后更新
13                 dp[k][j] += dp[k-type[i]][j-1];//状态转移
14 }
15 
16 int main(int argc, char const *argv[])
17 {
18     int s;
19     int ans[MONEY] = {0};
20     solve();//用dp计算完整的转移矩阵
21     for (int i = 0; i < MONEY; ++i)//对每个金额计算有多少种组合方案,打表
22         for (int j = 0; j < COIN; ++j)//从0开始,注意dp[0][0]=1
23             ans[i] += dp[i][j];
24     while(cin>>s)
25         cout<<ans[s]<<endl;
26     return 0;
27 }
硬币问题

 

posted @ 2020-03-13 23:01  DemonSlayer  阅读(172)  评论(0编辑  收藏  举报