• 基础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 }
    硬币问题
  • 相关阅读:
    Log4j日志
    Spring和MyBatis环境整合
    Hibernatede的优缺点,Struts的工作流程,延迟加载及理解开闭原则
    Java开发中的23种设计模式
    Spring配置
    Spring框架的一些基础知识
    python打造多线程图片下载工具
    redis 主从备份服务器集群搭建
    mongoo数据库设置权限
    mongo数据库主从备份服务集群搭建
  • 原文地址:https://www.cnblogs.com/125418a/p/12489728.html
Copyright © 2020-2023  润新知