Kai’blog

主博客 | 势利纷华,近之而不染者洁,不近者亦洁,君子不立危墙之下。

动态规划 总结

##引言——DP的本质

DAG上的动态规划与树形DP这两个词看上去很高大上,但实则就是记忆化搜索,而记忆化搜索其实就是DP的本质。当选择一个需要用全局变量来参与描述状态的方式时,就只能用常规搜索。但当状态可以完全被几个非全局参数确定性的描述时,就可以用记忆化搜索,记忆化搜索可以通过存储答案并直接提取来大大减少时间复杂度,可见重复状态越多、则记忆化搜索所带来的优化就越大,而当状态完全不重复时,记忆化搜索就退化成了最朴素的搜索,且无法使用全局变量的限制还凭空增加了思维复杂度,显然不划算。因此,我们只有会使用有意义的记忆化搜索,即因重复状态较多而可以带来较大优化的记忆化搜索。特别的,我们为这种记忆化搜索起一个好听的名字,这便是动态规划。

动态规划的核心在于用几个参数完全且确定地刻画某一个状态,然后依照状态间的关系进行记忆化搜索,特别的,因为动态规划的状态完全有几个参数表示,DP的状态因而可以被写在一个表格、一个长方体、或任何n维体上,且状态间的关系具有单调性,这使得我们可以按照一个较为简单的规则而非按照搜索的自然顺序来进行计算,且可以保证虽然并未按搜索的自然顺序来计算,但任何状态在他被用到之前都一定已经算好了,也就是说保证了结果的不变性。这种简单的规则通常是按行按列的填表法,或者其他能被几个循环简单描述的填表法,总之这种简单的规则太过于普遍的能够出现,且可以转递归为循环省去了栈空间,因而被广泛应用,甚至倒反天罡让初学者误以为这才是DP的自然形态,使得初学者无法意识到DP的本质因而极难真正理解DP。尽管如此,递推式DP确实好用,因而我们下文在DAG上的DP与树形DP这两个传统记搜领域以外,都将全部采用递推式DP来分析,不过学习者要始终记得DP的本质及递推式DP这种方式的本质。

##分类

背包DP,其实算不上是一种DP,只是为了解决背包问题而单独画出来的一个门类,实则就是用多个参数来刻画状态并递推式地进行状态转移。

区间DP,其实也只是一种特殊的二维DP,两个维度分别表示要描述的区间的左右端点,DP值则是描述该区间某性质的一个量值。

数位DP,也没什么新活,无非是将DP的一个维度用于表示正在处理某数字的第几位.

概率DP,无非是解决概率为题时用几个参数描述状态,然后推出了递推式,便称为概率DP了,其实也不是什么新玩意,一般而言少有单独出现的概率DP,往往是树上概率DP,或者DAG上概率DP。

树形DP,只要记住随手存一下siz数组,用于表示以该节点为根的子树的节点个数,然后用siz数组来进行优化,不要算一整行的dp,只算前siz个dp值,就可以很神奇的消掉一个维度的时间复杂度了;另外需要注意的是有的题需要换根技术,典型特征是要求求出所有点的解,而时间只允许求一个点的解。做法也不复杂,遇到这种题,只需要假定只要求求以1为根的一个解,然后直接做出来。做出来以后再来一次dfs,子母之间相互剔除影响,计算出以不同节点为根的答案,然后在把剔除的影响还原,标准dfs过程。就可以不额外花时间复杂度便求出所有点的解

状压DP,终于有点新活了,当我们面对大量小参数时,比如16个大小为1bit的参数,我们不应该用八个变量来表述这一个参数,因为一个变量(哪怕是bool变量)最小也要占8bit,这是由计算机的结构决定的。因此如果用变量来表达取值范围小于8bit的参数时势必会造成浪费,用于描述1bit的参数显然会造成巨量浪费,因此启发我们在一个变量的8bit当做八个小变量的集合,用一个short变量来表述16个0/1状态参数,从而大量节省空间,反向提取信息时只要利用位运算即可。注意,状压只适用于避免空间浪费的,可以在有大量小参数的情况下优化空间复杂度,但对时间复杂度并不起优化作用(但也几乎不会带来额外的时间复杂度,也就是说对于小于8bit的参数而言,状压DP思想带来的空间优化是免费的,不用白不用)

计数DP,这个其实更体现了DP本质是记搜,先想好暴搜怎么搜,然后在想上下界问题,之后做一个记忆化就成了。

状态DP、插头DP、动态DP,这些勉强也算是有点新活,但总的来说,DP所要学的理论确实少之又少,真正有用的,是下面这些优化思想

##优化思想

1.免费额度要利用

当DP函数的取值只有0和1时,或其他取值范围很小的时候,我们要时刻记住DP的取值实则就是免费赠与我们的一个维度,即二维DP实则可以描述一个三维逻辑空间中的状态,因为两个参数加一个DP值正好三维,可二维DP的时间复杂度却只取决于两个参数,不取决于DP值。因而DP值可以理解为免费额度,从占小便宜的角度来想,我们要尽可能的利用免费额度多占便宜,真要花自己钱的时候则要尽量节省。因此当DP函数的取值只有0和1时,或其他取值范围很小的时候,我们就要将DP值和某一个取值范围很大的维度的参数相调换,这样就几乎可以减去一个维度的时间复杂度,从而实现类似立方时间复杂度的算法化简为平方时间复杂度一类的操作

2.不要铺张浪费

当DP参数存在信息提供的重复性时,就存在降秩的可能,我们要尽可能避免一切重复提供的信息,这会造成巨大的时间浪费,对于完全或部分可以由其他维度来推得的维度而言,我们应当完全或部分的予以消除或替代。有时降秩的操作并不容易实现,需要先对DP状态转移方程进行转换后再进行降秩

3.贪婪地进行转移

有时在DP算法中使用贪心的思想可以大大减少时间复杂度,如果我们确信某些状态一定由具备某些特质的另一些状态转移而来(我们通常从状态转移方程中看出这一点,我们甚至有时可以通过对状态转移方程的变换而人为制造出这一点),那么我们就可以通过直接由后者确定前者,而不是使用所有的可能来更新前者,这有时可以足足减少一个维度的时间复杂度,可以说是巨大的优化。单调队列优化是这一原则的一个应用,但这一原则的更多应用是Meta的,也就是元问题式的,这很难概括,需要具体问题根据自己长期训练摸索到的指导思想来具体分析。

4.不要小看剪枝

有的时候DP数组并没有填满,大部分都是0,那么不算这些空间有的时候往往可以带来一个n->logn的优化甚至是一个直接优化掉一个维度的优化。有剪枝要大胆的写,有的时候剪枝的效果甚至会优于我们的预估

5.思考参数的上下界

有的时候某些看似很难存储的状态实质上取值范围很狭窄,对于这些状态不要盲目放弃,在纸上推一下真实取值范围,如果取值范围可以接受那就说明做法没问题,大胆的写

好困,先写个总结,以后再补

##总结

可见,DP其实没那么多分类,没那么多新知识,困难DP其实也不过是利用了更多DP优化思想,状态更难描述罢了,想要学会DP,一是要从本质上理解上述提到的所有理论知识,这是DP的本质,二是要大量做题,真正学会上面那些优化思想,并且总结出自己的优化思路,如此即可。

posted @ 2024-08-18 23:52  Kai-G  阅读(20)  评论(0编辑  收藏  举报
Copyright © 2019-2020 拱垲. All rights reserved.