[萌新向] 基础dp 笔记

 


dp基础

最基础的dp,侧重于讲一种思维方式,比较宏观

至于各种形式的dp,各种优化,甚至于如何写出来代码,不是本文的重点

具体的dp转移,可能不会给出

基本内容:本质与定义

dp:dynamic programming, 动态规划

dp的本质: 我们有一个要解决的问题,把问题分成若干步(dp的阶段), 每一步都有一个状态表示(dp的状态),然后考虑状态间的转移。

dp把问题通过某种顺序解决,使得我们可以 不记录 一些(通常来说,很多)东西,只记录我们 关心 的东西,从而比较高效的描述状态

所以,我们可以说,所有的dp都会有一个“状压”。只不过普通的dp压缩的比较浅显,而使用二进制的状压dp压的比较复杂,才叫它“状压dp”

就好比背包dp中,我们不暴力存下选择的物品的集合,而只记录选到第几个,占用多少空间,这也算一个压缩。如果不压缩,那dp数组可能是一个 map<set<int>,int>

起飞

确保基础知识理解之后,更多更难的dp题似乎就迎刃而解了

dp套dp

现在我们有一个问题,可以用dp解决。

那如果我们现在反手问一下,有多少个问题,跑一遍dp,能得到某个输出?

注意到这个dp(下面称为“内层dp”)已经帮我们划分好了阶段。我们直接以它划分的阶段为阶段,跑一遍dp。

dp的本质,特别适合被用来dp!

形式化一点讲,我们可以这样设计 dp

dp(u,S) 表示,在内层dp的第 u 个阶段,到达了 S 状态,方案数。然后依着内层dp的转移,进行外层dp的转移就行了。

详情见 这篇

选东西,带限制

这一类问题就是,你需要选择一些东西,但是有些限制,如xxx不能选,xxx不能同时选,...

解决这一类问题,需要观察限制的性质,然后设计一个处理问题的顺序,还有一个合适的状态,解决掉这个限制

如果限制长得不好看,那就转化一下,把它变好看

NOI2015 寿司晚宴

俩人选数,要求都得互质

互质 → 质因数集合没有交

注意到,n 的质数顶多被一个人选一次。而 n 的质因数,最多才 8 个。称那个 n 的质因数为 “大质因数”

限制的性质

把所有数按大质因数分类。对于同一种大质因数里面的,分类讨论:给A,或者给B

处理的顺序

对于小质因数,暴力记两个bitmask状压即可。

合 适 的 状 态 (迫真)

详解

十二省联考2019 皮配

我们发现某些学校有特殊偏好。但是我们发现这个数量非常少,所以,对于没有偏好的学校,可以先跑一遍不带限制的dp,再考虑这些有偏好的学校

有偏好的学校,就分类讨论一下所在的城市进哪个阵营,然后看学校如何选择导师即可。

把受限制的和不受限制的分开解决

这仅限一部分问题

这题的思维过程是挺套路,挺典型的。只是代码细节很多

详解

删东西,记剩下

我们每次要删掉一些东西,然后要决策怎么删,或者是求删剩下的结果。

我们可以用dp,记录当前删完剩下了啥。因为剩下的东西会继续反应,而删掉的就没了,所以我们只需要去关注剩下了的信息就行

对于剩下的信息,也可能要适当压缩一下。毕竟不是剩下的所有信息我们都关心。

EllysTournament

这题意,这数据范围,仿佛把 “区间dp” 四个大字写在了脸上

那我们需要关心的是,区间 [l,r] 里面比,每个人最终获胜的概率 ... 每个人吗?

可以是可以,完全没问题,但是开销太大了。

其实,我们只需要关注两端点(挺套路的转化)。对于 [l,r] 中的第 k 个赢下,可以看成是在 [l,k] 里面的最右边赢下来,然后在 [k,r] 里面的最左边赢下来。

那就设 L(l,r),R(l,r) 表示 [l,r] 里面 l/r 最终获胜的概率。枚举最后剩下的两个人,里面的另一个人,然后想一想 l/r 这个人怎么才能赢下,写出转移就行了

[THUSC2016]成绩单

同样考虑区间dp

我们关心什么信息呢?

注意到,我们甚至不关心当前剩下了几个,只需要知道min/max就行

f(l,r,L,R) 表示,区间 [l,r] 删完之后,数的值域在 [L,R] 里面,最小的代价。

这玩意看起来很好转移,然后就瞎几把做一波就没了

交换坐标轴

有时我们需要维护 f(x)=y 这样的一个函数

如果这个函数具有特殊性,并且 y 的范围小,x 的范围大,那我们可以转而考虑 y

大重量小价值的背包

这是我胡的一个题。以01为例吧

每个物品有重量 wi,价值 vi,背包是01的,总容量是 C,表示重量的和不能超过 C

如果 w 的范围小,v 就算是 1e9 也能做

f(w) 表示,如果装的重量不超过 w,最大收获的价值和

那现在反过来,如果 v 的范围是 [0,50],而 w 的范围是 [0,1e9],咋搞?

反过来,设 f(v) 表示,要达到价值 v,所需的最小的重量

我们发现,这个 ff 正好反了一下,这就是我们所说的 “交换坐标轴”

发现 f 同样很容易转移。最后就找一下最大的 v 使得 f(v)C 就行了。

某zr题

给两个串 S,T,每次给 [l,r],求 S[l:r]T 的最小编辑距离。|S|105,T20

参考 dyls的博客

观察他的博客,我们看到这一句话

继续优化,前面说过,F(x) 有一个很好的性质,就是值域很小。所以考虑将 DP 的下标与值域互换。

这就是我们讲的交换坐标轴的技巧。

这里也有另一种方法:我们自己来 构造 一个函数,使得它满足换轴的条件,我们再换轴维护。

至于这个 F 是如何想到的,可能得问问 dyls。这里给我个人的看法:

注意到两个串 A,B,长度为 n,m,它们的编辑距离在 |nm|,|n+m| 范围内。

我们发现它的区间跨度是 2×min(n,m)

这个很好证,发现这个东西关于 n,m 对称,不妨令 n<m,拆开得到跨度是 2n

放在本题里,它的跨度只有 2|T| 这么多,但是区间范围很大,是因为加了一个 |S| 上去。我们把这个 |S| 减掉,那它的值域就是 |T|,|T| 了。

所以我们才考虑 F(i)=ih(i) 这样一个函数。同时,考虑 h(i)i 也是可以的。前者增,后者减,只有一些微小的区别

小结

对于一个dp题,我们有时会觉得它复杂度太高。

此时,我们可以通过 观察,看看 哪个东西比较小,然后联想一下,能不能把复杂度往这里倾斜。这里用到的一个调复杂度的方法就是换轴,我们把下标,值两个东西反过来看,减小复杂度

naive-dp的变形

naive-dp指我们学dp的前几节课就学到的那些经典题

上面提到的 “大重量小价值的背包”,算是背包的变形。背包的变形我们见的多,这里不讲

编辑距离相关

上面提到了一个题,这里再讲一个题,BJOI2015隐身术

考虑最naive的编辑距离dp:f(i,j)=min(f(i1,j),f(i,j1))。而当 si=tj 时,f(i,j)=f(i1,j1)+1

我们可以把它看成是两个指针 p,q。当失配的时候,其中一个跳一步;否则,两个同时跳一步

我们注意到 k5,所以只会有 5 次失配。不失配的时候,用后缀数组直接匹配,否则就暴力搜,就行了。

LIS相关

LIS最naive的dp是两次方的

这里考虑一个不那么naive的dp,设 fi 表示,LIS要到 i,末尾的数最少是几

这是LIS的nlogn做法之一。每次我们贪心的lowerbound,更新 f

但是这个更新未免太简单了,让我们不禁想到:能不能更带劲点啊?

于是就有了下面的题:CF809D

相当于我们每次要把一个区间的数加到这个 f 里面去。

首先,对于 fi<lfi+1 可以用 l 去更新。即,若 fi<l,fi+1l,令 fi+1=l

对于 fi[l,r),我们可以往新序列里面加 [l,r] 中的任意整数。我们注意到 fi+1 肯定在这里面,所以可以用 fi+1 更新 fi+1。注意到 f 是增的,所以完全可以直接令 fi+1=fi+1,啥事没有

那现在我们要干的事情就是,找到分界点 i 使得 fi<l,fi+1l,把 fi+1 变成 l;再找一个 j 使得 fj<r,fj+1r,把区间 (i,j] 里的数 +1 并向后挪一位

向后挪一位,非常的熟悉啊,就是插入操作。由于前面正好有一个变成 l 的操作,我们可以直接看成是插入了一个 l 进去。然而这样长度不对,手玩一下,j 后面的那一个位置(如果有)要删一下。

然后就平衡树维护就行了

小结

对于这种经典题变形类的题,可以直接考虑原问题的dp,观察它的形式和性质,结合实际问题用一些东西优化两下子

(这里用到的“一些东西”,分别是:后缀数组;平衡树)

不要的不看

上面提到,我们的dp只关注需要关心的信息。对于不关心的信息,就不必记录在状态里

hdu6652 Getting Your Money Back

看到区间,很容易想到区间dp:设 f(l,r) 表示我们要取出的钱在 [l,r] 范围内,最坏情况多少额外花费。

它的复杂度爆炸,n3(设 n=rl)。别急,看我给它砍到 O(n)

首先发现,假设我们取了 k 块钱 lkr,接下来的转移只和 k相对位置 有关,也就是 kl+1 的值。为啥?因为我们取一次钱的额外花费只和相对大小有关,这是显然的。里面存 100 块我取 105 块,和里面存 10 块我取 15 块,都会得到一个 fail 然后花费 b 块钱,这和 100 还是 10 无关

所以,我们不需要管它 l,r 具体多少,只要有个长度就行。那我们的区间dp只需要记长度就行了。此时,砍到了 O(n2) 。注意一个细节是,左端点是否为 0 也有影响,要记一个 f(i) 一个 g(i) 表示左端点不是/是 0,长度为 i ,最优策略最坏情况额外花费。

Hint:这个“是不是0” 的本质是在考虑“左边那些要不要取”。想明白这个,下面的转移就能明白了

推一下 f,g 的转移式:

f(i)=min{max(b+f(j1),a+g(ij))}g(i)=min{max(b+g(j1),a+g(ij))}

内层 max 表示取最优情况,外层 min 表示取最优策略,里面 b+... 表示取失败,a+... 表示取成功。这是非常自然的一个转移

一增一减取 max,显然凸,来一个三分。尽管不太能过(网上说法,我自己没写),但理论上已经到 log 了,也很优秀

但是这东西还有一个性质就是它满足决策单调性。很容易想到,随着 i 的增加,这个凸壳凸起的那个点肯定是往右动的,所以就单调的增加 j 就行,复杂度严格线性。

CF1103D Professional layer

先把 gcd 求一遍。接下来,只有 gcd 的那些因子是有用的,其余质因子去掉即可

对于每个质因子,我们肯定只会选一个数来除。毕竟一个数除就能达到目的,选两个也没必要,还白花钱

假设一共有 m 个质因子。容易发现 m11。那么我们最多肯定只选 m 个数。

对于一个质因子,如果没别的限制,我们肯定选花费最小的那个除。但它可能已经被占掉了,再除就会超过 k 的限制。

但是我们发现它最多被占掉 m1 次。此时我们取第 m 小的就行。

所以,对于每个质因子,我们保留前 m 小的就行。

再进一步想,对于选择的一个质因子集合,要想除掉这一整个集合里的质因子,也只需要选能除这些质因子的数中,前 m 小的那些。

所以我们实际上只关心 m×2m 个数。

我们可以先按花费排序,然后跑一个状压dp,设 f(i,k,S) 表示考虑前 i 个,选了其中 k 个数除,搞掉 S 集合里的质因数,最小花费。

看起来状态数很多,但是我们可以用短短几个if排除掉大量状态。我们实际保留的状态只有 m×m×2m 种。

接下来可以枚举子集暴力转移。复杂度 O(m23m)。由于 m 小的离谱,直接过了

小结

如果你发现你会一个dp,但是时间复杂度有亿点大,可以想想哪些东西不要,就丢掉,从而优化复杂度

与贪心结合

dp是一个非常精确的东西,它无敌对,太对了。但是和贪心一比,又显得非常的慢

于是有这样的一个处理技巧,当数据非常大的时候,用贪心解决问题,把数据降下来;数据小的时候,再用dp解决问题

经典题:马跳棋盘

象棋的马,大家都比较熟悉。形式化的描述它的运动,就是每次可以在 x,y 坐标里,选一个 ±1,另一个 ±2。每次有 8 种跳法。

现在它从 0,0 出发要跳到 x,y。问最小跳多少步。0x,y1018

如果 x,y1000,那是傻逼题,在棋盘上写个dp就行

如果 x,y>1000 咋办呢?我们注意到此时 x,y 非常的大,宏观上,有如下贪心策略:把 x,y 里面绝对值大的那个 ±2,小的那个 ±1。容易发现,当 |x|,|y|1000 往上的时候,这样的策略肯定是对的。我们可以用一些除之类的计算,来优化这个过程到 O(log)

|x|,|y| 都在 1000 以内的时候,直接调用dp数组的值。

至此,我们做到了 O(logmax(x,y)) 的复杂度。

arc096d Sweet Alchemy

就是上面提到的那个“大重量小价值的背包”,多重背包版本。不但是个多重背包,选择的上限还是 1e9

我们发现,在大范围内,我们选性价比高的那个贪心是正确的。可以自己先想一下,这个范围是多少,以及为什么对,再看下去

这个范围是,当选的数 >n 个时,选性价比高的一定优

如果有两个物品 (w1,v1)(w2,v2),满足:

v1w1v2w2

此时假设我们选了至少 w12 物品。我们把这 w12 物品,换成 w21 物品,显然总重量不变,而价值变大了。

此时我们得到,当选的数 >max(w) 个时,确实性价比高的优。但是 max(w) 太大了,换一个角度考虑

假设我们选了至少 v12 物品。把它们换成 v21 物品,显然总价值不变,而重量变小了,同样也是更优的

但此时我们得到了更爽的一个界:只要选的数 >max(v) 个,就一定性价比高的优

v 就是子树 size,所以 max(v) 就是 n。证毕

在小范围内,再跑一遍上面提到的那个换轴dp,这题就没了

总结

dp的技巧非常多,就算不涉及到插头dp,斜率优化,四边形不等式优化...等等这些高级技巧,就单纯考思维能力,也有着许许多多的变数

思维一定要灵活,找到问题的本质并想办法优化求解的方法

所以,不要停下来啊

posted @   Flandre-Zhu  阅读(311)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示