[萌新向] 基础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的转移,进行外层dp的转移就行了。
详情见 这篇
选东西,带限制
这一类问题就是,你需要选择一些东西,但是有些限制,如xxx不能选,xxx不能同时选,...
解决这一类问题,需要观察限制的性质,然后设计一个处理问题的顺序,还有一个合适的状态,解决掉这个限制
如果限制长得不好看,那就转化一下,把它变好看
NOI2015 寿司晚宴
俩人选数,要求都得互质
互质 → 质因数集合没有交
注意到, 的质数顶多被一个人选一次。而 的质因数,最多才 个。称那个 的质因数为 “大质因数”
限制的性质
把所有数按大质因数分类。对于同一种大质因数里面的,分类讨论:给A,或者给B
处理的顺序
对于小质因数,暴力记两个bitmask状压即可。
合 适 的 状 态 (迫真)
十二省联考2019 皮配
我们发现某些学校有特殊偏好。但是我们发现这个数量非常少,所以,对于没有偏好的学校,可以先跑一遍不带限制的dp,再考虑这些有偏好的学校
有偏好的学校,就分类讨论一下所在的城市进哪个阵营,然后看学校如何选择导师即可。
把受限制的和不受限制的分开解决
这仅限一部分问题
这题的思维过程是挺套路,挺典型的。只是代码细节很多
删东西,记剩下
我们每次要删掉一些东西,然后要决策怎么删,或者是求删剩下的结果。
我们可以用dp,记录当前删完剩下了啥。因为剩下的东西会继续反应,而删掉的就没了,所以我们只需要去关注剩下了的信息就行
对于剩下的信息,也可能要适当压缩一下。毕竟不是剩下的所有信息我们都关心。
EllysTournament
这题意,这数据范围,仿佛把 “区间dp” 四个大字写在了脸上
那我们需要关心的是,区间 里面比,每个人最终获胜的概率 ... 每个人吗?
可以是可以,完全没问题,但是开销太大了。
其实,我们只需要关注两端点(挺套路的转化)。对于 中的第 个赢下,可以看成是在 里面的最右边赢下来,然后在 里面的最左边赢下来。
那就设 表示 里面 最终获胜的概率。枚举最后剩下的两个人,里面的另一个人,然后想一想 这个人怎么才能赢下,写出转移就行了
[THUSC2016]成绩单
同样考虑区间dp
我们关心什么信息呢?
注意到,我们甚至不关心当前剩下了几个,只需要知道min/max就行
设 表示,区间 删完之后,数的值域在 里面,最小的代价。
这玩意看起来很好转移,然后就瞎几把做一波就没了
交换坐标轴
有时我们需要维护 这样的一个函数
如果这个函数具有特殊性,并且 的范围小, 的范围大,那我们可以转而考虑 。
大重量小价值的背包
这是我胡的一个题。以01为例吧
每个物品有重量 ,价值 ,背包是01的,总容量是 ,表示重量的和不能超过
如果 的范围小, 就算是 也能做
设 表示,如果装的重量不超过 ,最大收获的价值和
那现在反过来,如果 的范围是 ,而 的范围是 ,咋搞?
反过来,设 表示,要达到价值 ,所需的最小的重量
我们发现,这个 的 正好反了一下,这就是我们所说的 “交换坐标轴”
发现 同样很容易转移。最后就找一下最大的 使得 就行了。
某zr题
给两个串 ,每次给 ,求 与 的最小编辑距离。
参考 dyls的博客
观察他的博客,我们看到这一句话
继续优化,前面说过, 有一个很好的性质,就是值域很小。所以考虑将 DP 的下标与值域互换。
这就是我们讲的交换坐标轴的技巧。
这里也有另一种方法:我们自己来 构造 一个函数,使得它满足换轴的条件,我们再换轴维护。
至于这个 是如何想到的,可能得问问 dyls。这里给我个人的看法:
注意到两个串 ,长度为 ,它们的编辑距离在 范围内。
我们发现它的区间跨度是 。
这个很好证,发现这个东西关于 对称,不妨令 ,拆开得到跨度是 。
放在本题里,它的跨度只有 这么多,但是区间范围很大,是因为加了一个 |S| 上去。我们把这个 |S| 减掉,那它的值域就是 了。
所以我们才考虑 这样一个函数。同时,考虑 也是可以的。前者增,后者减,只有一些微小的区别
小结
对于一个dp题,我们有时会觉得它复杂度太高。
此时,我们可以通过 观察,看看 哪个东西比较小,然后联想一下,能不能把复杂度往这里倾斜。这里用到的一个调复杂度的方法就是换轴,我们把下标,值两个东西反过来看,减小复杂度
naive-dp的变形
naive-dp指我们学dp的前几节课就学到的那些经典题
上面提到的 “大重量小价值的背包”,算是背包的变形。背包的变形我们见的多,这里不讲
编辑距离相关
上面提到了一个题,这里再讲一个题,BJOI2015隐身术
考虑最naive的编辑距离dp:。而当 时,。
我们可以把它看成是两个指针 。当失配的时候,其中一个跳一步;否则,两个同时跳一步
我们注意到 ,所以只会有 次失配。不失配的时候,用后缀数组直接匹配,否则就暴力搜,就行了。
LIS相关
LIS最naive的dp是两次方的
这里考虑一个不那么naive的dp,设 表示,LIS要到 ,末尾的数最少是几
这是LIS的nlogn做法之一。每次我们贪心的lowerbound,更新
但是这个更新未免太简单了,让我们不禁想到:能不能更带劲点啊?
于是就有了下面的题:CF809D
相当于我们每次要把一个区间的数加到这个 里面去。
首先,对于 , 可以用 去更新。即,若 ,令
对于 ,我们可以往新序列里面加 中的任意整数。我们注意到 肯定在这里面,所以可以用 更新 。注意到 是增的,所以完全可以直接令 ,啥事没有
那现在我们要干的事情就是,找到分界点 使得 ,把 变成 ;再找一个 使得 ,把区间 里的数 并向后挪一位
向后挪一位,非常的熟悉啊,就是插入操作。由于前面正好有一个变成 的操作,我们可以直接看成是插入了一个 进去。然而这样长度不对,手玩一下, 后面的那一个位置(如果有)要删一下。
然后就平衡树维护就行了
小结
对于这种经典题变形类的题,可以直接考虑原问题的dp,观察它的形式和性质,结合实际问题用一些东西优化两下子
(这里用到的“一些东西”,分别是:后缀数组;平衡树)
不要的不看
上面提到,我们的dp只关注需要关心的信息。对于不关心的信息,就不必记录在状态里
hdu6652 Getting Your Money Back
看到区间,很容易想到区间dp:设 表示我们要取出的钱在 范围内,最坏情况多少额外花费。
它的复杂度爆炸,(设 )。别急,看我给它砍到
首先发现,假设我们取了 块钱 ,接下来的转移只和 的 相对位置 有关,也就是 的值。为啥?因为我们取一次钱的额外花费只和相对大小有关,这是显然的。里面存 块我取 块,和里面存 块我取 块,都会得到一个 fail 然后花费 块钱,这和 还是 无关。
所以,我们不需要管它 具体多少,只要有个长度就行。那我们的区间dp只需要记长度就行了。此时,砍到了 。注意一个细节是,左端点是否为 也有影响,要记一个 一个 表示左端点不是/是 ,长度为 ,最优策略最坏情况额外花费。
Hint:这个“是不是0” 的本质是在考虑“左边那些要不要取”。想明白这个,下面的转移就能明白了
推一下 的转移式:
内层 表示取最优情况,外层 表示取最优策略,里面 表示取失败, 表示取成功。这是非常自然的一个转移
一增一减取 ,显然凸,来一个三分。尽管不太能过(网上说法,我自己没写),但理论上已经到 了,也很优秀
但是这东西还有一个性质就是它满足决策单调性。很容易想到,随着 的增加,这个凸壳凸起的那个点肯定是往右动的,所以就单调的增加 就行,复杂度严格线性。
CF1103D Professional layer
先把 求一遍。接下来,只有 的那些因子是有用的,其余质因子去掉即可
对于每个质因子,我们肯定只会选一个数来除。毕竟一个数除就能达到目的,选两个也没必要,还白花钱
假设一共有 个质因子。容易发现 。那么我们最多肯定只选 个数。
对于一个质因子,如果没别的限制,我们肯定选花费最小的那个除。但它可能已经被占掉了,再除就会超过 的限制。
但是我们发现它最多被占掉 次。此时我们取第 小的就行。
所以,对于每个质因子,我们保留前 小的就行。
再进一步想,对于选择的一个质因子集合,要想除掉这一整个集合里的质因子,也只需要选能除这些质因子的数中,前 小的那些。
所以我们实际上只关心 个数。
我们可以先按花费排序,然后跑一个状压dp,设 表示考虑前 个,选了其中 个数除,搞掉 集合里的质因数,最小花费。
看起来状态数很多,但是我们可以用短短几个if排除掉大量状态。我们实际保留的状态只有 种。
接下来可以枚举子集暴力转移。复杂度 。由于 小的离谱,直接过了
小结
如果你发现你会一个dp,但是时间复杂度有亿点大,可以想想哪些东西不要,就丢掉,从而优化复杂度
与贪心结合
dp是一个非常精确的东西,它无敌对,太对了。但是和贪心一比,又显得非常的慢
于是有这样的一个处理技巧,当数据非常大的时候,用贪心解决问题,把数据降下来;数据小的时候,再用dp解决问题
经典题:马跳棋盘
象棋的马,大家都比较熟悉。形式化的描述它的运动,就是每次可以在 坐标里,选一个 ,另一个 。每次有 种跳法。
现在它从 出发要跳到 。问最小跳多少步。
如果 ,那是傻逼题,在棋盘上写个dp就行
如果 咋办呢?我们注意到此时 非常的大,宏观上,有如下贪心策略:把 里面绝对值大的那个 ,小的那个 。容易发现,当 在 往上的时候,这样的策略肯定是对的。我们可以用一些除之类的计算,来优化这个过程到
当 都在 以内的时候,直接调用dp数组的值。
至此,我们做到了 的复杂度。
arc096d Sweet Alchemy
就是上面提到的那个“大重量小价值的背包”,多重背包版本。不但是个多重背包,选择的上限还是 。
我们发现,在大范围内,我们选性价比高的那个贪心是正确的。可以自己先想一下,这个范围是多少,以及为什么对,再看下去
这个范围是,当选的数 个时,选性价比高的一定优
如果有两个物品 ,,满足:
此时假设我们选了至少 个 物品。我们把这 个 物品,换成 个 物品,显然总重量不变,而价值变大了。
此时我们得到,当选的数 个时,确实性价比高的优。但是 太大了,换一个角度考虑
假设我们选了至少 个 物品。把它们换成 个 物品,显然总价值不变,而重量变小了,同样也是更优的
但此时我们得到了更爽的一个界:只要选的数 个,就一定性价比高的优
就是子树 ,所以 就是 。证毕
在小范围内,再跑一遍上面提到的那个换轴dp,这题就没了
总结
dp的技巧非常多,就算不涉及到插头dp,斜率优化,四边形不等式优化...等等这些高级技巧,就单纯考思维能力,也有着许许多多的变数
思维一定要灵活,找到问题的本质并想办法优化求解的方法
所以,不要停下来啊
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 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】