插入类 dp 总结
插入类 dp 总结
概念
\qquad 什么是插入类 d p dp dp 呢?
\qquad 这类题目都有一个特性:1、题目往往会基于一个给定的排列做 d p dp dp;2、时间复杂度在 O ( n 2 ) ∼ O ( n 3 ) O(n^2)\sim O(n^3) O(n2)∼O(n3) 之间;3、答案和排列中上升、下降的突变位置有较大关系。
\qquad 这类题目的套路也很固定:1、我们考虑将排列中的数按照从小到大的顺序插入其中,并以它为阶段进行状态设计。这样做有一个很好的性质就是前面已经插入的数一定比当前数小,可以不考虑上升下降的限制;2、状态一般设计为: d p i , j dp_{i,j} dpi,j 表示已经插入了 1 ∼ i 1\sim i 1∼i,插入的数字组成了 j j j 个连续段的方案数。这个连续段要求只要形成就不允许断开,但是段与段之间允许插入新的数字。这样我们可以仅通过合并段、放到段两边、新开一段这三种情况统计出所有的方案数。不过,要注意的是,在这里新开一段与放在段两边属于两种不同的方案,原因就是上面说的段与段之间允许插入新的数字。3、如果题目是最优化问题,涉及到贡献的统计,我们一般采用贡献提前计算的套路。
\qquad 这类题目一般还会限制这个排列的左右端点。在转移时特判左右端点的情况即可。如果没有限制,那我们就要加一维状态表示当前已经确定了几个端点。
例题
Permutation
\qquad 题面
\qquad 很经典的一道题。按照顺序插入的套路当然要使用,不过它的状态不能设计成上文提及的那样。因为这道题中,排列中的数的大小关系已经明确给定而且不规律,我们不能通过记录段数来进行转移。所以我们返璞归真,设 d p i , j dp_{i,j} dpi,j 表示已经插入了 i i i 个数,最后一个数在前 i i i 个数中的相对排名是 j j j 的方案数。注意是相对排名。为什么这么设计状态就可以呢?因为我们对于第 i i i 个数可以填什么只跟第 i − 1 i-1 i−1 个数填了什么有关,所以只记录最后一个数的状态足矣。转移分 > , < >,< >,< 两种情况讨论即可,很好转,需要加一个小的前后缀和优化。
\qquad Code
[ABC209F] Deforestation
\qquad 题面
\qquad 我们先考虑如何才能做到代价最小。因为是最优化问题,涉及到了贡献的计算,所以我们考虑贡献提前计算。既然想贡献提前算,考虑三个数之间的贡献是很不好搞的,所以我们考虑 i i i 与 i + 1 i+1 i+1 之间会产生什么贡献。若先拿走 i i i,产生的贡献是 a i + 1 × 2 + a i a_{i+1}\times 2 + a_i ai+1×2+ai,若先拿走 i + 1 i+1 i+1 产生的贡献是 a i × 2 + a i + 1 a_i\times 2 + a_{i+1} ai×2+ai+1。通过这个不难发现,我们先拿走 i i i 与 i + 1 i+1 i+1 之间更大的那个显然是更优的。按照这个策略,我们就能轻易的做到代价最小。接下来统计方案该怎么搞呢?
\qquad 因为取数的最优策略已经定了,所以我们从这个策略入手。如果 a i > a i + 1 a_i>a_{i+1} ai>ai+1,那么我们就会先拿走 i i i,这相当于 i i i 的相对排名比 i + 1 i+1 i+1 高(假设我们先拿走相对排名高的), a i + 1 > a i a_{i+1}>a_i ai+1>ai 同理。如果 a i = a i + 1 a_i=a_{i+1} ai=ai+1,那么 i i i 和 i + 1 i+1 i+1 先拿走哪个都无所谓。这样,这道题就转化为了上一题,把上一题的做法套过来即可。
\qquad Code
[CEOI2016] kangaroo
\qquad 题面
\qquad 这道题就可以完美的套用插入类 d p dp dp 的所有套路。我们设 d p i , j dp_{i,j} dpi,j 表示插入了 1 ∼ i 1\sim i 1∼i,形成了 j j j 个连续段的方案数。先不考虑端点,我们插入第 i i i 个数后有三种情况:1、新开一段。对应了 d p i , j = d p i − 1 , j − 1 × j dp_{i,j}=dp_{i-1,j-1}\times j dpi,j=dpi−1,j−1×j,因为有 j j j 个空位可以插入。在前面我们提到,新开一段意味着两边以后可能会插入新的数字,而新的数字一定比 i i i 大,所以符合题目要求;2、合并两个连续段: d p i , j = d p i − 1 , j + 1 × j dp_{i,j}=dp_{i-1,j+1}\times j dpi,j=dpi−1,j+1×j。这两个连续段在插入 i i i 之前就已经存在了,一定比 i i i 小,符合要求;3、放在一段的边缘。不过,我们想这一情况合法么?放在边缘意味着另一边以后可能会插入更大的数字,这样就不符合题目的要求了。
\qquad 现在,我们考虑有端点的情况。设左右端点分别为 s , t s, t s,t。1、新开一段。还是要从 d p i − 1 , j − 1 dp_{i-1,j-1} dpi−1,j−1 转移而来,不过乘的系数会有所变化。如果有端点已经插入,那么我们就不能在端点外边继续新开一段。所以对应的转移式是: d p i , j = d p i − 1 , j − 1 × ( j − ( i > s ) − ( i > t ) ) dp_{i,j}=dp_{i-1,j-1}\times (j-(i>s)-(i>t)) dpi,j=dpi−1,j−1×(j−(i>s)−(i>t));2、合并两个连续段。在这里我们要特判端点,因为端点一定不能合并。此时有一个小细节:如果 i > s & & i > t & & i < n & & j = 1 i>s\,\&\&\,i>t\,\&\&\,i<n\,\&\&\,j=1 i>s&&i>t&&i<n&&j=1,此时我们是不能转的。因为此时一旦合并,就将 s s s 和 t t t 合并为同一段了,不过后面还有数字没插入,一定不合法。剩下的和上边一样。3、放在一段的边缘。有端点之后,不难发现端点可以放在两边两端的边缘。不过 s s s 只能放在最左边, t t t 只能放在最右边。这样,这道题就做完了。
\qquad 核心 C o d e Code Code:
Ant Man
\qquad 题面
\qquad 最优化问题,考虑贡献提前计算。状态设计就是把记方案数改为记最小代价。分的情况与上一题几乎一样,多处理一下放在段两边的情况,贡献简单分讨即可。
\qquad Code
[JOI Open 2016] 摩天大楼
\qquad 题面
\qquad 实在是一道好题。
\qquad 因为本题要求的是 s u m ≤ L sum\leq L sum≤L 的方案数,所以我们要在状态中加一维 s u m sum sum。又因为本题没有限制端点,所以我们还要再加一维表示确定了几个端点。综上,最终的状态为: d p i , j , k , l dp_{i,j,k,l} dpi,j,k,l 表示已经插入了前 i i i 个数(从小到大排序后),形成了 j j j 个连续段,这些连续段产生的总代价和为 s u m sum sum,确定了 l l l 个端点的方案数。由于 s u m sum sum 这一维的跨度可能会很大,最大范围能到 n L nL nL,所以如果直接转复杂度为 O ( n 3 L ) O(n^3L) O(n3L),不可过。我们考虑优化。
\qquad 我们想,如果在相邻两个位置分别填了 A 1 A_1 A1 和 A 2 A_2 A2,那它们产生的代价就是 A 1 − A 2 A_1-A_2 A1−A2;如果填了 A 1 A_1 A1 和 A 3 A_3 A3,那代价就是 A 1 − A 3 = ( A 1 − A 2 ) + ( A 2 − A 3 ) A_1-A_3=(A_1-A_2)+(A_2-A_3) A1−A3=(A1−A2)+(A2−A3)。往后以此类推。发现什么规律了吗?对于当前我们考虑插入的数 A i A_i Ai,我们可以在所有连续段端点处都加上 A i − A i − 1 A_i-A_{i-1} Ai−Ai−1,这样并不会影响最终代价的计算,但是它使得 s u m sum sum 的跨度急剧减小。因为此时 s u m sum sum 一定是单增的,所以如果此时 s u m > L sum>L sum>L 就不再往下转移即可。时间复杂度降为 O ( n 2 L ) O(n^2L) O(n2L)。
\qquad 核心 C o d e Code Code:
[ZJOI2012] 波浪
\qquad 题面
\qquad 与上一题一模一样,只不过把 % m o d \%mod %mod 去掉,最后除一个 n ! n! n! 即可。
\qquad 这道题最大的坑点在 K ≤ 30 K\leq 30 K≤30,我们需要对“数据范围分治”:对于 K ≤ 8 K\leq 8 K≤8,我们正常用 d o u b l e double double;对于 K ≤ 30 K\leq 30 K≤30,我们使用 _ _ f l o a t 128 \_\_float128 __float128 即可。
\qquad 核心 C o d e Code Code(指 _ _ f l o a t 128 \_\_float128 __float128 的输出):
Phoenix and Computers
\qquad 题面
\qquad 这道题可以继续沿用上面的套路,但是没必要,因为 n ≤ 400 n\leq 400 n≤400。
\qquad 我们直接大力设状态: d p i , j dp_{i,j} dpi,j 表示开启了前 i i i 台电脑,其中有 j j j 台是手动开的,方案数。在转移前,我们要先预处理一个 g i g_i gi:完全靠手动开启 i i i 台电脑的方案数。想求 g g g,我们可以逆向考虑:假设现在有 i i i 台电脑开着,我们每次只能关最两边的其中一台电脑,求方案数。这个问题很简单: 2 i − 1 2^{i-1} 2i−1,所以 g i = 2 i − 1 g_i=2^{i-1} gi=2i−1。在这之后,我们就可以正常 d p dp dp 了:枚举最后一段连续段长度为 k k k,则 d p i , j = d p i − k − 1 , j − k × g k × C j k dp_{i,j}=dp_{i-k-1,j-k}\times g_k\times C_j^k dpi,j=dpi−k−1,j−k×gk×Cjk。
\qquad Code
[COCI2021-2022#2] Magneti
\qquad 题面
\qquad 套路的,我们设计状态: d p i , j , k dp_{i,j,k} dpi,j,k 表示前 i i i 个磁铁分为 j j j 组,占用空位数为 k k k 的方案数。转移正常分类讨论即可。不过,因为我们这里设计的是分了 j j j 组,而不是有 j j j 个连续段,所以转移时乘的系数会有所变化。最后统计 a n s ans ans 时要乘个组合数。这个组合数的含义是将剩下的空位插入到 n n n 块磁铁之间。根据插板法得到 a n s = ∑ d p n , 1 , i × C n + l − i n ans=\sum dp_{n,1,i}\times C_{n+l-i}^{n} ans=∑dpn,1,i×Cn+l−in。
\qquad 核心 C o d e Code Code:
__EOF__

本文链接:https://www.cnblogs.com/best-brain/p/18006547.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)