动态规划入门

”动态规划“这个名字不好理解,初次听到也没什么概念,后来接触的时候发现和算法也没什么关系。据说是数学领域内迁移到cs领域的,算法大意是:结果是通过决策一步步积累出来的。

解决的问题

最优化问题:最值,方案数。

算法

本质:递推。

算法本身可以理解为对递归的优化,十分类似记忆化搜索,基本是一个思路不同的实现方式。

参考文章中这句话——核心思想均为: 利用对于相同参数答案相同的特性,对于相同的参数(循环式的dp体现为数组下标),记录其答案,免去重复计算,从而起到优化时间复杂度的作用。这,便是二者的精髓。

理解算法:(参考:参考文章)

换句话说我们需要一个决策,但这个决策太大了,我们做不了,所以需要把他递归到我们可以简单做出决策的状态,然后从这些状态开始,慢慢的“动态地”演进到最终的决策。

比如说用最少的硬币换零钱,突然和你说要换78分钱,你怎么就能迅速给出答案呢,你不能。但是如果是1分的话,你就可以,2分的话呢,就是在1分的基础上再加1分,你也可以。于是你就慢慢地从1分开始一直算到78就有答案了。从另一个角度说,如果你用DP算出了怎么换78分,那如果我问你76分怎么换,你也应该有答案了。

所以在DP的实践中很重要的就是递推关系和边界条件。所谓边界条件就是最简单的情况,所谓递推关系就是如果你已经知道这么多,再多给你一个,你怎么得到。

说一个最最最简单的例子,找出一个数组中的最大值。这个问题边界条件是什么呢,就是如果只有一个元素,那最大值就是他;递推关系是什么,就是你已经知道当下最大的数,再多给你一个数你怎么办。你会拿这个数和当下最大的数去比,其中较大的那个就是新的最大的数。这就是典型dp的思想。所以不要把DP看的过于高深就好了。


算法本身不难,难的是如何使用dp,基本只能通过解决常见类型问题积累经验。

需要了解的一些概念(下面了解即可,都是一些名词概念,解题的时候不可能单独去想这些名词型的东西)

两个要求:

  • 最优子结构:大问题的最优解可以由小问题的最优解推出

  • 无后效性:未来与过去无关。小问题的求解过程过程不影响大问题的解

两个元素:

  • 状态:事物的发展过程表现出的所有形态(这里指通过小问题推导到最终问题解的过程中所有的子问题的解可以理解为一个子问题,求解的过程进行到了哪一步(哪一个子问题)。

  • 转移:从小问题推导到出大问题的解的这个过程。

判断是否可dp:

  1. 根据 无后效性 分析:如果对于某个状态,可以只关心值,而不需要关心如何求解的(转移的),也可以根据数据范围来辨别,一维106左右,二维103

  1. 状态定义全靠经验

  1. 状态转移方程就是对最后一步的分情况讨论。而方程就是表述的状态定义,所以如果猜出一个状态定义,但发现列不出覆盖所有情况(不漏)的方程这就是状态定义错了。

  1. 状态转移方程确保不漏 或 不重不漏

  • 如果是求最值的话,我们只需要确保「不漏」即可,因为重复不影响结果。

  • 如果是求方案数的话,我们需要确保「不重不漏」

  1. 复杂度:状态数量 * 计算次数

例题: 最长上升子序列

最优子结构:

为了计算第i个位置的最长上升子序列长度,对于所有小于i的位置j,可以计算出第j个位置的最长上升子序列长度,如果j < i则以j位置的LIS加上i构成第i个位置的LIS

无后效性:

只关心第i个位置结尾的LIS长度,不关心具体序列(长什么样子)

状态:

f[i],表示第i个位置结尾的LIS的长度。

转移:

对于一个位置 i,为了计算f[i],枚举子序列中上一个元素的位置 j 满足j < i且 a[j] < a[i],有f[i] = max(f[i], f[j] + 1)。满足不漏

目标状态:max(f[1~n])

dp解题步骤:

一切都以状态表示为基础,无论是状态计算还是确定初始状态,都要在状态定义的基础上定义
集合划分: 80%dp划分依据:最后一步来划分
  1. 状态表示:(根据题意确定是几维的状态)

  • 再确定下标以及值的含义(<=> 集合和属性的含义)

  1. 状态计算:根据状态表示的含义分析递推公式(<=> 划分集合写出子集对应的递推方程)

  1. (边界)起始状态:

  1. 目标状态:

  1. 状态初始化:

  1. 确定遍历顺序:

  1. 举例推导dp数组:

dp调试

  • 举例推导状态转移

  • 打印dp数组

  • 是否和推导一致?

你可能感兴趣的:(算法,动态规划,算法,c++)