syt:什么是动态规划?
辣鸡:动态规划可以说是一个算法,但是不如说是一种思想
syt:所以动态规划到底干啥的?
辣鸡:动态规划可以利用相邻状态之间的递推关系,利用O(n)的复杂度去解决暴力起来要高到O(n2)甚至是O(n3)的问题,你说厉不厉害?
syt:雀食蟀啊!不过动态规划有啥特点吗?
辣鸡:动态规划说实话没啥特点,有的时候你可能都不知道这个问题能不能利用dp的思想去解,不过一般dp都和贪心有着密切的联系,一般贪心能解决的问题dp也可以解决,一般贪心解决不了的问题dp也可以解决,说白了一般贪心发现解决不了的时候,有很多很多特殊情况的时候,可能dp就要派上用场了
syt:说了这么多,dp的核心思想是什么啊?
辣鸡:dp的核心思想是利用相邻状态之间的关系进行递推演算和状态转移,每一个新的状态的值都是从旧状态转移过来的,所以在dp的过程中经常利用一个方法:刷表法(把每一个状态都遍历到,让每一个状态都要有值,这样去看状态如何转移的比较清晰明了)
syt:可是这样我并不能看出来dp相对于直接暴力有什么优势哇,不也是要把每一个状态都进行遍历到吗?
辣鸡:我举一个例子你就知道了,利用dp我们是要去找我们所想要的答案,对于中间过程的一些不需要的答案我们就不会再去关照,其实这点和递归有异曲同工之妙,这也是为什么人们都把记忆化搜索称为小dp了
辣鸡:比如我们举例01背包的例子,我们定义了f[i][j]表示用j容量的体积去装前i件物品所得到的最大价值,那么在求f[n][m]的过程中,我们势必要遍历f[1…n][0…m],显然是个O(nm)的算法,那么在这个过程中我们关注的是最大价值,和具体怎么选的有关吗?当然没有关系,我们只需要知道用j体积的包去装前i个物品能获得的最大价值是多少即可,不需要去列出具体是拿了哪件物品和没有拿哪件物品。
辣鸡:但是如果我们按照暴力的思想,利用递归去求01背包问题,对于每一个物品要么选要么不选,那么最后我们肯定是可以得到一个选取的具体方案,是基于具体方案确定的最后结果,那么时间复杂度就跟具体的选取方案有直接关系,这样时间复杂度不就是O(2n)嘛,这和基于关注的最大价值确定的结果的算法时间复杂度简直是天差地别
syt:哦~~ 我知道了,所以说dp是只关注最后需要的那个变量的值,与最终所需要答案无关的别的变量意义其实并没有那么重要,是基于结果的一个算法,所以有的时候可以节省很多的时间复杂度,对吗?
辣鸡:雀食,有一些道理,dp的奥妙还是很深的,针对不同的问题具有不同的解法和转移思路,学习dp的过程中对于状态转移方程的含义理解很重要,学好dp其实很有意思,你会发现自己的脑子变得灵活了很多,所谓dp的学习,最重要的就是变通和多角度全面思考。
syt:666
动态规划原理——加法原理和乘法原理
分类加法原理:
做一件事,完成它可以有n类办法,在第一类办法中有m1种不同的方法,在第二类办法中有m2种不同的方法,…,在第n类办法中有mn种不同的方法,那么完成这件事共有
N=m1+m2+m3+…+mn种不同方法。
分步乘法原理:
做一件事,完成它需要分成n个步骤,做第一步有m1种不同的方法,做第二步有m2种不同的方法,…,做第n步有mn种不同的方法,那么完成这件事共有N=m1 x m2 x m3 x … x mn种不同的方法。
动态规划的相关概念
动态规划:动态规划是解决多阶段决策过程最优化问题的一种方法。
阶段:把问题分成几个相互联系的有顺序的几个环节,这些环节称为阶段。
状态:某一阶段的出发位置称为状态,通常某一阶段包含若干状态。
决策:从某个阶段的一个状态演变到下一个阶段某状态的选择。
策略:由开始到终点的全过程中,由每段决策组成的决策序列称为全过程策略,简称策略。
状态转移方程:前一阶段的终点就是后一阶段的七点,前一阶段的决策选择导出了后一阶段的状态,这种关系描述了由i阶段到i+1阶段状态的演变规律,称为状态转移方程。
动态规划使用的基本条件
分解子问题
- 求证的问题可以分解为子问题,并且可以由子问题得到这个问题。
- 子问题作为新问题也可以各自分为各自的子问题。
最优子结构
问题的最优解必然包含着其子问题的最优解
(对于当前阶段,无论前面阶段如何决策,当前阶段从上一阶段产生的决策一定是全局最优的)
无后效性
动态规划只适用于解决当前决策与过去状态无关的问题。
也就是说当前任何决策都不会对接下来的决策产生可执行性的影响。
举个栗子:如果当前i阶段执行了j策略,那么在k阶段就不能执行q策略(其中i<k)这个时候k阶段的决策的执行性就收到了影响,这样就不具有无后效性。
这个时候一般去开辟新的空间,分别去转移i执行j的时候和i不执行j的时候的状态
雨巨dp步骤:
初步dp&线性dp例题:
例一:数字三角形
链接: [P1216 USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
题目要求:从第一层走到第n层所经过的路径最大值
也就是说求从第一层的起点到第n层的n个点之间距离,取最大值
变量:定义f[i] [j]表示从起点(1,1)到点(i,j)所得到的路径最大值
决策:对于每一个状态,可以做的决策是向左下走,或者向右下角走。
转移:从第一层到n-1层各个点转移到第n层各个点
第i层第j个点,可以从第i-1层第j个点和第i-1层第j-1个点转移过来
初始状态:f[1] [1]=a[1] [1]
状态转移方程:f[i] [j]=a[i] [j]+max(f[i-1] [j],f[i-1] [j-1]);
例二:过河卒
链接: [1008-NOIP2002]过河卒_2021秋季算法入门班第七章习题:动态规划1 (nowcoder.com)
思路:
题目要求:从(0,0)到(n,m)之间的路径条数
变量:定义f[i] [j]为(0,0)到(i,j)之间的路径条数
决策:对于每一个状态,可以做的决策是向右或者向下走
状态转移:对于位置(i,j),可以从(i-1,j)或者(i,j-1)转移过来
限制条件:对于马的位置(x,y),f[x] [y]=0,并且马可以一步到达的位置(dx,dy),f[dx] [dy]=0
状态转移方程:
f[i] [j] =f[i-1] [j]+f[i] [j-1] (不满足限制条件)
f[i] [j] =f[i-1] [j] ((i,j-1)是限制条件)
f[i] [j] =f[i] [j-1] ((i-1,j)是限制条件)
例三:传球游戏
链接: [1009-NOIP2008]传球游戏_2021秋季算法入门班第七章习题:动态规划1 (nowcoder.com)
思路:
题目要求:求经过m次传到1号手里的传递方式
变量:定义f[i] [j]表示经过i次传到j的手里的传递方式,最终求f[m] [1]
决策:对于每一个状态可以做的决策是向右传或者向左传
状态转移:对于某个人j,其一定是从j+1或者j-1转移过来
因此f[i] [j]+=f[i-1] [j-1]+f[i-1] [j+1]
限制条件:对于第n个人,n+1是1号,对于第1个人,1-1是n号
状态转移方程:
f[i] [j]+=f[i-1] [j-1] (j!=1)
f[i] [j]+=f[i-1] [n] (j==1)
f[i] [j]+=f[i-1] [j+1] (j!=n)
f[i] [j]+=f[i-1] [1] (j==n)
例四:最长不下降子序列(LIS)
思路:
所求问题:长度为n的字符串的最长不下降子序列
变量:定义f[i]为前i个字符并且以i 为结尾的最长不下降子序列
决策:对于每一个状态,可以做的决策是将当前字符加入子串中,或者不将当前字符加入字串中。
状态转移:对于a[j]<a[i] (j<i),那么f[i]可以由f[i]转移过来,即f[i]=f[j]+1
最终所求:f[1…n]最大值
初始化条件:每个数单独均为一个长度为1的LIS,因此f[i]=1
状态转移方程:
f[i]=max(f[i],f[j]+1) (对于任意的i和j,均满足j<i)
例五:滑雪(记搜)
链接: [P1434 SHOI2002]滑雪 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
题目所求:从任意一个点按照任意方向下滑能滑的最大距离
变量:定义f[i] [j]为从(i,j)开始下滑可以到达的最大路径
决策:对于每一个状态可以做出的决策是,向上下左右四个方向可以到的地方走。
限制条件:必须从高到低滑,每次可以往上下左右四个方向
状态转移:对于(i,j),假设可以向四个方向去滑,那么取最大值+1即可。if(a[tx] [ty]<a[x] [y]) 则f[x] [y]=max(f[x] [y] ,f[tx] [ty]+1);
转移方程:
初始状态:本身为1
ps:由于可以任意向四个方向去扩散,for循环的递推转移不再适用,一般用记忆化搜索进行状态的转移。
例六:最大子段和
链接: P1115 最大子段和 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
题目所求:对于一个长度为n的序列,求某一段的字段和最大的值
变量:定义f[i]为以i为结尾的最大子段和,最终求f[1…n]最大值
决策:对于每一个状态,可以做出的决策是将此时的字符加入前面序列,或者从当前字符开始作为新的序列。
状态转移:
对于状态i,如果以i-1为结尾的最大子段和+a[i]是大于a[i]的,那么就接一起,反之自己独立。
状态转移方程:f[i] = max(f[i-1]+a[i],a[i])
例七:最长公共子序列(LCS)
链接: P1439 【模板】最长公共子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路:
题目所求:P1字符串前n个和P2字符串前n个的最长公共子序列的长度
变量:定义f[i] [j]为P1字符串前i个字符和P2字符串前j个字符的最长公共子序列的长度
转移条件:对于某一状态(i,j)满足a[i]==b[j],则f[i] [j]=f[i-1] [j-1]+1
状态转移方程:
f[i] [j]=f[i-1] [j-1]+1(a[i]==b[j])
f[i] [j]=max(f[i-1] [j],f[i] [j-1])