各种DP分类

基本原理

三个条件:

  1. 最优子结构:能将大问题分解成小问题,并且大问题的最优解能通过小问题的最优解构成。
  2. 无后效性:已经求解的子问题,不会再受到后续决策的影响。
  3. 子问题重叠:可以用数组存下重叠的子问题来提升效率。

基本思路:

  • 将原问题分解成若干个阶段,找出每个阶段对应的子问题的特征(称为状态
  • 找每个状态可能的决策,即各状态之间的转移方式(称为状态转移方程
  • 找方程的边界,确定转移的顺序

(可以按顺序递推着写,不好确定顺序时也可以记忆化搜索

本质上可以对应到图上,状态为节点,决策为边,构成DAG,按拓扑序递推。

线性DP

LIS & LCS

LIS(Longest Increasing Subsequence 最长上升子序列)

计算序列A的LIS

Sol:

定义fi表示考虑到前i个数,以第i个数结尾的LIS长度(状态)。

fi=maxj<iaj<aifj+1(状态转移方程)

f1=1(边界)

从前往后转移即可(顺序)。

最后ans=maxi=1nfi

O(n2)

Another Sol:

上面的算法太慢了,考虑优化。

fi表示所有长度为i的LIS最后一项的最小值,特别地,若长度为i的LIS不存在,则fi=INF

下面证明:随着i增大,fi单调递增。

运用反证法。假设存在x<i,使得fx>fi,那么在以fi结尾的长度为i的LIS中可以分离出以fi结尾的长度为x的LIS,这与fx的最小性矛盾。所以命题得证。

现在来考虑以i结尾的LIS的长度dpi,由于我们要计算所有满足aj<aijdpj的最大值,不妨设最大的dpj=x

由于ai>ajfxaj,故总有ai>fx

又由于fi单调递增,因此我们要找到最大的x满足fx<ai,考虑二分。

二分出x后,dpix+1即可。还要更新f,我们只需更新fdpi,这是因为f1,f2fdpi1都小于aifdpi是第一个大于等于ai的。

O(nlogn)

其他类似问题,最长下降子序列,最长不升子序列,最长不降子序列,考虑的方式是类似的。所以记住LIS的推导即可。

LCS(Longest Common Subsequence 最长公共子序列)

求序列A和序列B的LCS

Sol:

定义fi,j表示考虑序列A中的前i个以及序列B中的前j个时LCS的长度。

考虑转移:

fi,j=max(fi1,j,fi,j1,fi1,j1+[ai=bj])

前两项是继承,第三项是尝试拓展。

边界:f0,0=0

从前往后转移即可。

O(n2)

Another Sol:

上面的算法太慢了,考虑优化。

我们把序列A离散化成一个排列,序列B与之对应。

再将排列中的每个数对应到它的下标,则序列A变成一个从1n的序列,序列B与之对应。

此时LCS长度不变(这由映射关系是显然的),并且注意到LCS的长度等于序列B的LIS的长度。

于是转到对LIS的优化。

(注意序列B中的元素要都在序列A中出现过才能使用这种方法)

O(nlogn)

背包

01背包

n个物品,背包容量为m,每个物品有体积vi(有时也叫重量或者其他类似概念)和价值wi,每个物品只能取1次,求在物品总体积不超过背包容量的情况下能得到的最大价值。

Sol:

定义fi,j表示考虑了前i个物品,背包容量为j时可以得到的最大价值。

fi,j=max(fi1,j,fi1,jvi+wi)

前一项是继承,后一项是尝试做选择第i个物品的决策。

f0,0=0

从前往后转移即可。

O(nm)

代码实现可以压掉第一维,但注意转移顺序,要倒着转移(显然的,看转移方程)。

回退背包

自己起的名字。可能不叫这个。

01背包算好后其中的物品是无序的,可以随便钦定一个物品作为最后一个加入的,然后倒着跑背包撤销这个物品的影响。

注意这个东西只能在信息支持简单撤销时能用,比如加法改成减法,求max之类的就不行了。

P4141 消失之物

完全背包

与01背包类似,但一个物品可以取无限次。

有两种解决方式:

The First Sol:

把每种物品拆成每2k个一组(二进制分组),显然分的组是有限个,对这些组跑01背包。

The Second Sol:

同01背包定义fi,j

fi,j={fi1,jj<vifi,jvi+wiOtherwise.

注意到与01背包的区别只在于做决策时的下标。

第一维同样可以压掉,但是注意转移顺序,正着转移即可。

O(nm)

多重背包

与01背包类似,但第i个物品有si个。

Sol:

考虑二进制分组,每2k个分一组,最后不足的单独分一组。

可以证明这样可以组合出每一种决策。

然后转化为01背包做。

O(nmlogs)

混合背包

01背包、完全背包、多重背包混在一起。

Sol:

分类讨论一下,把三种代码拼在一起。

或者都用二进制分组。

二维(多维)费用背包

在背包物品要消耗体积外还有其他费用(比如重量、时间等)。

Sol:

多开几维,同样可以把枚举第i个物品的第一维压掉。

分组背包

与01背包类似,但是每组内的物品只能选一个。

Sol:

每组之内做一次01背包。

具体而言,定义fi,j表示考虑到第i组,背包容量为j时能获得的最大价值。

每一组内枚举其中的物品尝试转移。

更一般的线性DP

难点在于消除可能的后效性。

在线性DP中我们通过观察题目性质和优化状态设计来做到这一点。

有时会设计出形如fi,s的状态表示考虑了前i个,最后一组的状态为s时的最优解,一般可以把s这一维优化掉。

区间DP

区间DP其实是特殊的线性DP,以区间长度为阶段。

基本思想是从小区间转移到大区间。

一般很板,就是从小到大枚举区间,然后枚举断点转移。

状态一般设为fl,r表示[l,r]这段区间的最优解。

有二维的形式,也类似,状态设为fx1,y1,x2,y2表示左上角为(x1,y1),右下角为(x2,y2)的矩形的最优解,枚举断点改为枚举断开的行或列。

处理环

一般断环成链,将原数组复制一份接在后面即可。

转移涉及当前区间最值

可以看做是在笛卡尔树的结构上进行DP。

状压DP

本质是将一种状态压缩为一个数进行DP,除此之外与其他DP类似。

注意可以预处理出所有可行状态以提高运行效率。有时这样复杂度才正确

高级的状压:插头DP。

树形DP

一般的树形DP

只是将DP的操作放到树上,利用树很好的递归性质求解。

一般定义状态为fu,s表示在u的子树内限制为s的最优解。

尝试从son/fa转移。

树上背包

就是把背包放到树上。

定义状态为fu,i,j表示在u的子树内,考虑了前i个儿子,背包容量为j时能获得的最大价值。

转移:fu,i,j=maxvsonu,kjksizvfu,i1,jk+fv,sizv,k

注意边界,手推一下。

第二维可以压掉,但要倒序枚举j(显然的,看转移方程)。

树上换根

也叫二次扫描,通常要两次DFS。第一次进行一些预处理,第二次开始DP

转移时可以考虑从父亲转移到儿子(因为一般以1为根时的答案可以在第一次DFS后简单地算出来,然后向下转移)。

关注换根时答案的变化量。

数位DP

问题特征

  • 目的是统计满足某种限制的数的个数。

  • 限制是针对数位的。

  • 提供统计的区间或上界。

  • 上界很大,但只看数位个数可以接受。

基本原理

有一个通用答案数组,其中记录在计数过程中大量重复出现的过程的答案(如统计10001999的答案和20002999的答案,后三位都是000999,可以单独记录下来)。

这个通用答案数组根据题目限制进行DP

区间的询问通常拆成两个区间相减。

统计答案部分可以记忆化搜索,也可以递推。从高到低枚举每一位,注意贴上限时的处理(如上限为12345,现在填的前三位为123)。

部分套路:

i位上能填的数与前面填的数相关。

定义状态为fi,s,op表示当前考虑到第i位,前缀的状态为s,第i位与前缀的关系为op

根据题意尝试转移。

还可以尝试继续压缩常数,参数里面能相互推的压在一起。

记忆化搜索

记搜大法吼!

记搜长得很板,方便拓展。

一般先将边界数字拆到数位上,然后从高位到低位填数。用个数组记忆化一下。

一般有以下形参:

  • pos:当前枚举到的位置。

  • lim:第pos位是否受限(前几位是否贴上界)。初始时可以理解为前面都贴了0的上界。

  • lead:第pos位前面是否有前导零。前导零与答案有关时才记,零只有在不是前导零时才算贡献。

接着是根据题意得到的限制条件,记搜时要作为状态的一部分塞到形参里以及状态里。

考虑用于记忆化的数组ffi,op表示当前[len,pos+1]都填好了,且满足限制op[1,pos]随便填(这就要求!lim&&!lead(没有限制)才能返回f)的答案。

计数DP

特征:计算满足限制的方案总数。

注意计数时要求的不重不漏。有重复时考虑容斥减掉或者设计一种DP顺序使方案不重复。

一般是数学推式子再套上DP

概率期望DP

就是概率期望相关的数学推式子套上DP

一些套路:

对于概率DP,一般是正着DP,即定义状态fi表示从初状态到状态i的概率。

对于期望DP,一般是倒着DP,即定义状态fi表示从状态i到末状态的期望。(有些题目也可以正着DP,但是有些麻烦)

插头DP

连通性相关的状压DP。

适用于各种网格覆盖相关且允许状压的问题。

SOS DP

Sum Over Subset DP,子集和DP。是特殊情况的高维前缀和。

每一维只是0/1的情况比较常见,比如二进制数视作集合,然后求ai中子集的和。

表述更为清晰一点的例子是,求a1n中有多少个i满足ai&k=k

复杂度与值域有关,设值域为V,则复杂度为O(VlogV)

现在来讲做法,设fi,j表示前i位与j可以不同,其他位必须相同的子集的和。

那么就有分讨了:

  1. j的第i位为0,那么必须选0fi,j加上fi1,j

  2. j的第i位为1,则fi,j加上fi1,j+fi1,j2i

这样就不重不漏地数完了。

超集和DP也是类似的,分讨一下就做完了。

和数位DP有一定相似性,但侧重点不同。数位DP擅长处理一段连续区间中的数数问题,而SOS DP擅长处理不那么连续,而是较为离散的点的数数问题,而且点的权值可以任意给,缺点是复杂度太高。

有后效性的DP的处理方法

一般是按某个值排序(如DP值,而且不是真的排序,只是规定了顺序)。

然后可以按照类似跑最短路的方式转移。

posted @   RandomShuffle  阅读(60)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示