dp 大典
作为 OI 里面分支最多的模块之一,dp 在 OI 中有着重要的作用,现在,让我们一起走进 dp 的世界:
注:我在每道题前面都标注了个人难度,范围大概是
1|0AT_dp 系列
众所周知,Atcoder 中有一套全是 dp 的题目,难度大致逐渐增加,我们可以从中学到很多 dp 的知识。
1|1AT_dp_a ~ AT_dp_c
个人难度:
这部分非常的简单,初学 dp 的人也不难做出来。
1|2AT_dp_d(背包)
个人难度:
背包问题的板子:
我们发现本题为
1|3AT_dp_e
个人难度:
和上一题题面完全一样,但背包容量最大能到
注意到
最后找到
1|4AT_dp_f(LCS)
个人难度:
LCS 板子,dp 部分很简单:
但是此题还要输出方案,这是本题的难点。不过没关系,我们只需要从最后一步开始,倒着进行 dp 的过程,寻找上一次从哪转移来的即可。
1|5AT_dp_g
个人难度:
这道题很简单,求有向无环图最长路,其实里面也蕴含了一个技巧,就是 DAG 上的 dp。对于这种 dp,我们可以在拓扑排序过程中进行 dp:
1|6AT_dp_h
个人难度:
和过河卒是一样的,挺简单的。
1|7AT_dp_i(概率 dp)
个人难度:
难度从这一刻起来了。
这是一道概率 dp,但是还不算太难。遇到这种正面反面的问题,可以考虑设
对于这个题,统计答案只需要枚举向上的硬币数量,累加 dp 值即可。
1|8AT_dp_j
个人难度:
这个题也是概率 dp,但比上一个难很多。
首先对于随机选盘子而言,盘子的顺序对期望值没有影响。这一点对于本题是很重要的。
因为
但是这样状态是
1|9AT_dp_k(博弈 dp)
个人难度:
这算是一个很初级的博弈 dp,因为状态和转移都很简单。
考虑设
1|10AT_dp_l(区间 dp)
个人难度:
这种双端队列两人取数,肯定最先想到区间 dp。于是可以设
这个区间 dp 和平常我们做的(如石子合并)还是有一些不同的,因为它不用枚举区间端点。
当然本题有
1|11AT_dp_m(前缀和优化 dp)
个人难度:
这种问题通常涉及到前面一些连续 dp 状态的和,这时我们可以用前缀和来优化转移。大体是这样的:
也是非常的好理解。
1|12AT_dp_n
个人难度:
这就是我们常说的石子合并问题了,同样用区间 dp 解决,转移方程也是非常直接:
其中
1|13AT_dp_o(状压 dp)
个人难度:
从这里开始难度又上升了一个档次。
求二分图完全匹配数量?看上去是一个很经典的问题,但是普通的 dp 状态难以解决这个问题。注意到
大家都知道,二分图是有两部分点的,鉴于时空都有限,我们只能表示出一个集合。所以我们设
转移的时候,枚举刚才说的
时间复杂度
1|14AT_dp_p(树形 dp)
个人难度:
这是一道比较简单的树形 dp。
对于这种涂黑涂白的问题,可以设
1|15AT_dp_q(数据结构优化 dp)
个人难度:
带权 LIS,十分经典的问题。
在
我们如果从前往后更新
这时我们相当于要维护一个集合,支持加入数,查询
其实还是挺好写的,熟悉了之后
1|16AT_dp_r(矩阵快速幂优化 dp)
个人难度:
求长度为
我们可以设
这和矩阵乘法完全一样,所以直接对邻接矩阵矩阵快速幂即可。
1|17AT_dp_s(数位 dp)
个人难度:
这种求
注意到
实现方式通常采用记忆化搜索的方式,从高位往低位填数:
1|18AT_dp_t
个人难度:
我们已经来到了本套题最难的几道题目。
排列计数问题,我们发现排列具体是什么我们不关心,只需要知道相对大小顺序即可。
于是设
-
字符为
,此时上一个位置的排名必须比 小,即 。 -
字符为
,此时上一个位置的排名必须大于等于 ,即 。为什么会有等于?因为
的排名比 小,所以原本排名为 的会变成 ,故可以取到等于。
观察两个方程,发现可以前缀和优化到
1|19AT_dp_u
个人难度:
哦,
注意到我们甚至可以把每个子集的贡献都预处理出来(设为
然后设
这个也很好理解,我们只需要把这部分分成某个子集和其余部分,然后取最大值就可以了。
问题就是,后面这部分时间复杂度是
但是真的是这样吗?注意到,我们如果不重不漏地枚举子集,那么其实时间复杂度是:
也就是说,我们只需要一种能不重不漏枚举子集的方法就好了!
这种方法当然是存在的,它就在这里:
所以按照刚才的方法 dp 即可。
1|20AT_dp_v(换根 dp)
个人难度:
这个染出来的连通块是无根树,所以树形 dp 状态就很难设了。
所以考虑先设
这是很好理解的,因为每个子节点
然后再设
其中
然后根据乘法原理,
1|21AT_dp_w
个人难度:
这绝对是很难的,起码对于没有见过线段树优化 dp 套路的人来说,而且这题题解很魔怔,我感觉一半都有问题。
首先,我们把每个操作的贡献绑在右端点上,下面所有的 dp 状态也基于这个给出,可以证明这样可以不重不漏。
设
这个只有在
这玩意复杂度大到没边了,考虑先滚动数组一下:
我们换个角度,从操作的角度考虑,每个操作贡献的事实上就是
于是就可以用线段树
1|22AT_dp_x(贪心优化 dp)
个人难度:
对于这种问题,可以拎出来两个位置
如果
所以按照
1|23AT_dp_y(dp+排列组合)
个人难度:
神秘的状态设计方式……
设
其中
为了好写可以把终点当作第
1|24AT_dp_z(斜率优化 dp)
个人难度:
boss 题来了!
显而易见得到
然后可以化简
我们可以把与
设
再用 Exchange arguments 的技巧,若
整理得:
哎这不是我们斜率的式子吗?
所以设
所以我们维护一个下凸壳就可以解决这个问题了!
对于下凸壳的维护,我们可以采用单调队列,具体来说步骤如下:
-
若前两个元素不满足前一个元素更优,将前一个元素出队。
-
此时头元素为最优转移点,转移
。 -
把队尾不优于
的出队。 -
把
入队。
2|0AT_tdpc 系列
好的我们成功的来到了下一个系列,做好准备了吗?让我们更深一步走进 dp!
(那些紫题有点难,先咕一会)
3|0别的 DP 技巧
这里是一些别的 dp 技巧,可能有很多是打 ABC 时我没做出来的题。
3|1AT_abc385_g(多项式优化 dp)
个人难度:
首先先想不算很难的
转移比状态简单:
这个时候就要观察了,发现转移用到的全是
然后化整式后 NTT 即可。
3|2AT_abc386_f(去除 dp 无用状态)
个人难度:
哎这不是我们编辑距离问题吗,等等为啥是
哦原来是判定性问题,而且
想想我们编辑距离问题里设的状态,
然后这时只有
3|3AT_abc389_g(巨大计数 dp)
个人难度:
这不太像人类能出出来的题:求满足与
然后我们发现:
这下可做了,但是注意到
这下大概只有一种可能了,就是正解的时间复杂度可能是
这种计数题必须把状态设全,才能保证转移的过程中能转。首先这题一个很重要的性质是,这个满足条件的图大概是这样的:
我们发现图按照到一号点的最短路距离进行了分层,且有以下性质:
-
号点在第一层,且第一层只有 号点。 -
( )层的任意点必须和 层中的至少一个点连边。 -
层内随意连边。
-
只有相邻的层有边相连。
-
奇数层和偶数层总点数相等(这图没体现这点,因为是我乱画的)。
然后我们看看 dp 需要什么:
-
现在是第几层。
-
这层有多少点。
-
一共有多少点,多少边。
-
奇数层有多少点、偶数层有多少点。
你可能会问为什么不需要知道这层有多少边,这个等会转移的时候我们就知道了。
现在开始设状态。注意到知道了“奇数层有多少点、偶数层有多少点”,也就知道了一共有多少点。而且“现在是第几层”其实不重要,只需要记录该层的奇偶性(当然只是减少了空间)。所以设
接下来是转移。我们发现
其中
这个时候我们就发现了,没有当前层的边数照样也能转移,所以根本不需要记录。
然后是
这个
分析一下复杂度,瓶颈在
3|4AT_abc391_g(dp 套 dp)
个人难度:
我们想一下我们平时是怎么做 LCS 的,设
-
若
, 。 -
否则,
。
现在要求满足条件的
那么问题来了,这个东西的状态看起来很多,我们应该如何优化?
事实上,我们可以先把
-
先预处理出来每个差分数组后面添加每个字符会变成的差分数组。
-
转移时枚举后面是哪一位,更新 dp 状态。
这两部分都可以采用状压处理,总时间复杂度为
__EOF__
本文链接:https://www.cnblogs.com/Milthm/p/18705956.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!