连续段dp总结

名字是乱取的。感觉是个比较智慧的算法。

一、算法思路

在一些数排列的问题中,往往会遇到感觉是 dp 但是状态都列不来的情况,而连续段 dp 就是一个解决排列计数的利器。

具体思路是依次插入每个元素(通常是排序后从小到大/从大到小)。考虑当前元素插入到哪个位置,这样的话状态就需要记下当前插到了哪个数以及当前连续段个数。

转移时考虑:当前元素新开一个连通块,接在连通块的首/尾,连接两个连通块。

连通块 dp 之所以能多项式复杂度,很重要的一点是因为抓住了状态的共性,没有记录连通块具体状态与连通块之间间隙。

其实不是很能说清楚,直接看例题吧。

upd:在网上看到另一种理解连续段dp的方式是将其理解为建立笛卡尔树的过程。想了一下觉得挺形象的,毕竟排了序的,那么新开一个连通块就是新建叶子节点,合并就是把两颗子树合并为一颗并以当前节点为根,接在首/尾就是选一颗子树作为当前节点的儿子,这样当前节点只会有一个儿子。

最近发现很多人类智慧 dp 都可以转化为笛卡尔树后更加自然的想到,抽个时间把笛卡尔树补一下,快一年了已经忘完了。

二、例题

[CEOI2016] kangaroo

这个题数的就是一个波浪型的 1n 的排列。

然后我们考虑从 1n 依次插入。这样在插入的时候当前数一定比已经插入的大,就能确定波浪的形状了。

所以 fi,j 表示插入到 i,连通块数为 j 的方案数。

转移按照上面考虑。

注意到我们不能考虑接在连通块首尾的情况,因为在这道题中它已经在其它两种情况中被算到了,而且这种情况还转移不了。想一下波浪的形状就知道为什么了。

fi1,j=fi1,j1×j+fi1,j+1×j

然而当 i>s,i>t 时不能在首或尾单独开连通块(位置已经被占了),处理一下。

i=si=t 时再特殊处理一下。

[COCI2021-2022#2] Magneti

这题能做出来基本就掌握了。我相信新手刚学这个是做不出来这道题的,跟我一样

先把磁铁按照 r 从小到大排序。

fi,j,k 表示插入了 i 个磁铁,j 个连通块,占用了 k 个空位的方案数。

这里的连通块指的是:这些磁铁之间任意删除一个空都会导致它们吸引。

转移按照上面考虑。

  1. 当前元素新开一个连通块:fi,j,k+=fi1,j1,k1×j

  2. 接在连通块的首/尾:fi,j,k+=fi1,j,kri×2j

  3. 连接两个连通块:fi,j,k+=fi1,j+1,k2×ri+1×j

统计答案插板一下即可,不是重点,不再赘述。

值得解释的是,为什么上面可以这样转移,即为什么新开一个只用了 1 个空位,而接在首尾却用了这么多,合并连通块用得更多。

注意到已经把 r 排序了,当前连通块如果新开一个,后面要合并的话间隔多远取决于 r 大的那个,这就同时解释了 1,3 情况中 k 的变化。2 情况同理,与 r 小的接一起距离多远它说了算,而后面合并/接磁铁隔多远还得后面的磁铁说了算。

其实我觉得这题不会不光是连通块 dp 不熟练,这个连通块的定义确实有点不同。

「JOI Open 2016」摩天大楼

这个题呢其实涉及到另外一个很重要的套路。所以我认为 dp 初学者即使看完例1后搞懂了连通块 dp 这两道例题都做不起很正常。

直接做的话,首先肯定要按 A 排序,然后你会发现需要记录下每个连通块的状态才能算那个绝对值,根本优化不动。

引出一个处理绝对值的技巧:考虑每个绝对值必定能表示为 i=lrAi+1Ai 的形式。

我们对于每个 Ai+1Ai 计算其贡献次数即可。

在经过连通块 dp 后这个东西是很好算的。因为在插入完 Ai 后我们就能知道 Ai+1Ai 的贡献次数了,就是当前连通块端点个数。因为每个端点后面都会插入更大的数,Ai+1Ai 必定会产生贡献。

为了计算连通块端点个数需要记录下当前是否开头结尾已经被占用,也就是多少连通块只有一个端点。显然最多只有两个。

所以设状态 fi,j,k,l 表示当前 插入到 Aij 个连通块,绝对值和为 kl 个连通块只有一个端点。

到了这里应该不难自己推出转移了。方程可以直接看代码

本文作者:zqs2020

本文链接:https://www.cnblogs.com/stinger/p/16471744.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   zqs2020  阅读(399)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.