学习笔记——线性动态规划
线性动态规划是OIer学习DP的基础,只有学好了线性DP,才能更好的理解之后学习的背包、区间DP、树形DP等一系列难题,那么废话不多说,开始我们的学习之旅吧!
1.DP的概念
1.
2.
我们身为OIer自然要更加重视知识的关联性。运用类比的思想,我们可以发现DP与图论这两个看似毫不相干的两个知识竟然有着千丝万缕的联系。
综上所述,状态、阶段和决策是构成DP算法的三要素,而子问题重叠性、无后效性和最优子结构性质是问题能用DP求解的三个基本条件。
2.线性DP的定义
如果一个DP算法的状态包含多个维度,但在每一个维度上都具有线性变化的阶段,则该DP算法称为线性DP。
3.线性DP的核心思想
首先,我们可以先用自己的方式进行题意简述,把握题目大意,再想好每一个状态的状态表示及阶段划分,并在此基础上推出状态转移方程,最后确定DP的边界条件及DP目标后就可以开始码代码了。(这里推荐画一个表格,这样更能一目了然地看出状态转移方程)。
举个栗子(LIS问题--最长上升子序列)
| 题意简述 | 给定一个长度为 |
|---|---|
| 状态表示 | 令 |
| 阶段划分 | 子序列的结尾位置(数列 |
| 转移方程 | |
| 边界条件 | |
| 目标 |
4.线性DP的一些优化技巧
1.在设计DP的状态转移方程时,可以以如何计算出一个状态 (直接)的形式给出,也可以考虑一个已知状态应该更新那些后续阶段的未知状态(间接)。它们各有千秋,视具体题目选择合适的方法更有利于解题。
2.滚动数组真的好用:我们对于一些可能MLE的DP状态进行分析,如果我们发现这一状态的DP只与前几状态的DP有关,就可以只定义这个数+1个的该维度空间(举个栗子:若i的状态只与i-1的状态有关,则对于i的维度可以只开到2的空间,循环使用0和1)。但是滚动数组会覆盖掉之前求的状态,如果只用求最终状态则建议使用滚动数组进行优化,若要求每一个状态的最优解,千万别用!(不过这类题一般不会爆MLE,要不然就无解了)(滚动数组具体用法参照T7我的代码)
3.在实现状态转移方程时,要注意观察决策集合的范围随状态的变化情况,若决策集合有规律的变化,就可以考虑维护一个新的变量记录决策集合的当前信息,避免重复扫描,把转移的复杂度降低一个量级。(要栗子的话就是T2我用优化打了个N方DP过了NlogN的题(与数据水也有很大的关系))
别问为什么没有代码,DP这毒瘤玩意根本没有模板
好了,最后我再扔几道题就闪吧!
1.线性DP的入门水题(一篇比较了DP与贪心、搜索、记搜复杂度的好题解)(码了个10min竞速代码)
通过这道水题,我知道了两点:
(1).我的手速真的不行,码代码码不快(慢慢练吧)
(2).没有立刻想到最优解(明显从下传到上比从上传到下更快,因为前者只用输出
2.栈+二分、树状数组版二维偏序吊打DP的伪DP题(通俗易懂的栈+二分题解)(还不会的树状数组版二维偏序)(DP思路+DP常数优化+我的代码)
3.正反两遍最长上升子序列的水题(题解)(我的代码)
4.做两遍线性DP的水题(题解)(我的代码)
5.四种牌分别处理的线性DP(有点背包的味道)(讲的不错的题解)(我的代码)
6.第一个自己独立做出的DP绿题(O(N)做法的题解)(二分做法的NlogN的题解)(我的代码)
7.滚动数组+三维DP(讲的很详细的题解)(我的代码)
随手取模真的很重要,不然100pts->60pts。以后遇到这类题也不用太慌,先明确自己设定的DP数组每个维度都代表着什么(最好在代码中的相应数组下写一点注释),然后考虑未知的状态可以由那些已经求出的状态推出(可以在草稿纸上写出DP方程式),明确思路后再码代码。
8.构造新数列求最长不下降子序列+推新结论的恶心题(有动图的良心题解)(码了半天的代码(一个晚上+半个上午))
比较难的DP题真的就是在比耐心和细心。
问题一:本题运用正难则反的思想,对于暂时毫无思路的问题,可以考虑构造新数列进行求解(可以将题面简化为一堆关系式,然后对其进行化简,找出关系式中的共同点,在将其用一个新数组存储,可以极大的简化代码、降低复杂度和辅助思考)。
那个推新结论是人能想到的吗?
问题二:对于一些更难的问题,可以多画图辅助思考,排除一些矛盾的情况,找到一些类似的情形,然后转化为DP的形式进行状态转移方程的推导。
9.第一道独立做出的DP紫题(当然要写一篇题解好好庆祝)
10.双DP数组+前缀和优化的水题(讲的不错的题解(只看思路就可以打代码))(我的代码)
最后再挖两个坑,以后再填吧!(毕竟现在不是刷黑题的时候)
1.学完高数再做的黑DP
2.打完猪国杀之后用耐心做的大模拟+DP