动态规划的建立与优化
概述
动态规划(Dynamic Programming)本质上是将一个复杂的大问题分解为具有相同性质的小问题的解决问题的思路。这种思想在OI中是极为重要的(of vital importance)。因此OI中许多非最优化问题的递归/递推解法也被非正式地称作动态规划。
那么何时才会使用动态规划?很多时候都可以。每当觉得一道题非常神仙时,都可以使用动态规划的思想尝试去解决它。很多时候以为做不出来,其实都是可做的。
动态规划的建立
动态规划由两大部分组成:1.状态设计 2.状态之间的转移
状态设计要求选手在该状态能够转移的前提下,使用尽量少的参数来描述尽量多的子问题。
而状态间转移则是在状态设计的基础上,考虑下一步应该如何进行决策,从而转移到子问题。以下列举几个经典案例:
背包dp
有若干物品,每个物品有价值 \(v_i\) 以及花费 \(w_i\) 。现在你有 \(s\) 元钱,你要合理选择使得最终得到价值最高。
状态设计:记 \(f_S\) 表示选择的物品集合为 \(S\) 时的最大代价?状态数太多而一个状态所描述的同类子问题太少,不行。记 \(f_{i}\) 表示考虑前 \(i\) 个物品时能得到的最大价值?无法转移。记 \(f_t\) 表示当前还剩下 \(t\) 元钱时所能获得的最大价值?每个物品仅能选择一次,如何知道哪些物品已经被选择从而不能再选?因此无法转移。但如果记 \(f_{i,j}\) 表示仅考虑前 \(i\) 个物品, 现在恰好花了 \(j\) 元钱时的最大价值,就可以转移了。(但这并不意味着这是唯一的状态定义方法)
转移:考虑决策,即当前物品究竟是买还是不买,那么就有:
小结
通过这个案例,你会发现状态设计和其转移之间的强关联性。一个状态设计是有意义的当且仅当它是能够转移的。 从背包dp可以看出序列类的dp通常都需要钦定只考虑前 \(i\) 个从而进行转移。
区间dp
该类dp的状态通常都是一个区间。为什么我们需要一个区间表示状态?为什么不能像之前那样只考虑前 \(i\) 个?不是参数会更少吗?这是因为一个参数已经不足以描述子问题的性质从而导致无法进行转移。
区间dp特征是会涉及到区间的合并/分裂。其本身思想并不复杂,因此不再赘述。
树形dp
树这个结构本身就具有递归性质,因此dp和树契合得非常好。树形dp的状态设计中一般都需要记录当前位于哪个节点 \(u\) 。其他的参数就根据题目本身推演了,不过可以大致分为以下三类:
- 仅关于 \(u\) 子树的参数
- 仅关于 \(u\) 到根的路径的参数
- 仅关于整棵树除 \(u\) 子树外的部分的参数(换根dp就是这种定义)
小结
到这里,相信你对状态设计已经有了一定了解了。至于状压dp,数位dp等无非只是换汤不换药而已。
在有了良好的状态设计后,状态转移方程也很容易写出了。而状态转移方程能否写出则取决于状态设计。这就是它们间的强关联性。
动态规划可看作一个DAG:点代表状态,而边代表转移。一个点的值如何得到?通过连向这个点的边从而转化到求解另一些点的值,而“另一些点”则代表着相对原来的点的子问题——限制类似但规模更小。
动态规划的优化
动态规划的时间复杂度=状态数*转移复杂度
因此如果要进行优化,那么要么优化状态数,要么优化转移复杂度。
状态设计的优化
这部分没有什么套路,只能因题而异。不过硬要说还是有一些:
- 若发现答案的范围远小于某状态的范围,可以考虑将答案作为状态,而将那个状态作为答案。
- 计数类的dp可以考虑使用组合意义进行优化,经典案例就是WC数树。
- 观察是否有状态可以由其他状态推出,如果有,直接删去该状态。
- 重新从另一个角度思考问题,列出另一个dp
- and so on
状态转移的优化
单调队列/单调栈优化
动态转移方程形如
需要满足 \(l_1\le l_2\le\cdots\le l_n,r_1\le r_2\le\cdots\le r_n\) 。这就是一个滑动窗口类似的东西,直接使用单调队列即可做到线性。
斜率优化
动态转移方程形如
本质上是求 \(\max(f_j+g_ih_j)\) 。考虑令 \(t=f_j+g_ih_j\) ,移项可得
于是问题相当于平面上有若干点 \((h_j,f_j)\) ,现在给定斜率 \(-g_i\) ,需要最大化斜率。因此维护一个凸包,每次二分斜率找到最优位置即可。
不过还有另一种看待方式:
即问题相当于平面上有若干直线 \(f(x)_j=h_jx+f_j\) ,每次询问一个横坐标 \(g_i\) ,求最大纵坐标。使用李超线段树进行维护即可。
四边形不等式优化
这部分笔者不甚了解,在实际考试中也没怎么出现过,主要是考前一个结论的记和考场上一个表的打。不过为了完整性,这里还是挂一个同机房巨佬的链接。
数据结构优化
这种情况非常灵活,不过也比较简单。其实就是根据列出的方程写出数据结构,不再赘述。
cdq分治优化
动态转移方程中有三组大小关系的限制,因此考虑使用cdq分治类似三维偏序的方法去掉限制从而做到快速贡献。
wqs二分优化
这个东西可能不太算得上是dp优化??但我也不知道放在哪里,干脆就列在这里吧。
它所针对的一般是要求选择恰好 \(k\) 个物品时的最优值,同时满足不带限制时的答案是好求的。
令 \(g(x)\) 表示恰好 \(x\) 个物品时的最优值,那么必须要满足 \(g(x)\) 是一个凸函数,即其导数有单调性。
既然有单调性,那么我们可以考虑二分斜率找到切点以及对应选择了多少个物品,然后根据和 \(k\) 的大小关系判断应舍弃左半区间还是右半区间。
如何找到这个切点呢?切点处需要满足 \(f(x)=g(x)-kx\) 取得峰值。根据组合意义, \(x\) 指选择的物品个数,因此我们可将每个物品的贡献都减去 \(k\) 再求出最优解。此时的最优解就是切点。
长链剖分优化
用于树形dp中。其特征是存在一维只和深度有关。于是长链剖分后,dp到 \(u\) 时直接继承重儿子信息,然后对轻儿子信息暴力合并。由于任意一条链只会在链头被暴力合并,因此总复杂度为线性。