Diu 的 dp 题单

原本想写学习笔记的,但是 dp 题目太多,套路太深,我把握不住,所以我决定改成写题单。

本题单主要收录一些 dp 题目的神秘操作,当然也有一些套路比较常见,只是选一些做代表收录。


CF1626 A Random Code Problem

期望 dp,桶:开一个桶维护 fi,j 表示操作 i 次之后共有多少个 j,对于每一个 dp 值统计答案,设 L=gcd(1,2,,k1),发现 LaiL 的部分始终不变,统一统计即可。

P3736 [HAOI2016]字符合并

区间 dp 和状压 dp:设 fi,j,s 表示区间 [i,j] 的串最后合并成串 s 的最大分数。发现因为每次合并的价值均为正数,所以能合并肯定合并,所以最后一个固定的区间 [i,j] 会合并成长度为 (ji1)%(k1)+1 的串,直接暴力 dp 即可。

P4007 小 Y 和恐怖的奴隶主

矩阵优化,卡常:发现 m 很小,但是 n 很大,考虑直接开 m 维维护血量。设 fi,a,b,c 表示攻击 i 次,有 a 个奴隶主血量为 1b 个奴隶主血量为 2c 个奴隶主血量为 3 的期望打掉 boss 的血量,直接从 fi 暴力构建转移矩阵到 fi+1,复杂度 O(T1663logn)。多组数据时,一个优化是把 2 的次幂的矩阵预处理出来,然后每次二进制拆分,用一个行矩阵(或列矩阵)乘上需要的,优化到 O(1663+T1662logn)。还需卡常,在矩乘时用一个 __int128 做中间量,最后统一取模。

P4590 [TJOI2018]游园会

状压,dp 套 dp:字符串计数,考虑 fi, 表示前 i 个字符,满足什么状态的方案数。对于 NOI 的限制,我们可以考虑加一位 0/1/2 表示和 NOI 的匹配程度。对于最长公共子序列,我们可以考虑还原在 dp 求最长公共子序列的过程。如果把兑奖串看做已知,那么设 dpi,j 表示第一个串前 i 个字符,第二个串的前 j 个字符匹配了多少个,那么 dpi,j=max(dpi,j1,dpi1,j,dpi1,j1+[si=sj])。发现 dpi 的转移只和 dpi1 有关。我们可以考虑用一个状态 s 存储一个 dpi,然后每次转移暴力解压出来,更新完之后再压缩回去。但是这样做状态数很多,我们可以发现 dpi,jdpi,j1=0/1,所以我们可以把 dpi 差分起来,再状压。

CF1648C Tyler and Strings

计数 dp,数据结构优化:我们从小到大枚举每一位 i 表示 sti 位相同的情况,更新桶,表示当前可用点。如果第 i+1 位使得 s 字典序更小,那么后面的可以乱填。那么假设 ci 是桶,对于一个 j<ti+1,我们的方案数是 1cjci!,然而每次枚举一位只会改变一个 ci 的值,所以可以用数据结构维护这个过程。具体地,可用线段树,也可用树状数组维护前缀和。

SOJ 4. Journey

期望树形 dp,容斥,卡常

SOJ 5. Festival

期望计数 dp

SOJ 6. Random

期望 dp,矩阵优化,cdq

以上三题做法太神仙,笔者太弱,无法很好总结,另见 solution

SOJ 1.博弈

博弈论,DAG 图上 dp,背包 dp:可以发现每个点有无棋子对答案贡献互相独立,可以用一次 dp 解决。可以先设 fv 表示节点 u 可以使得当前先手领先多少个点,对于一个可以转移的 v,如果 u,v 同色,那么 fufv+1,否则 fufv+1fu 在所有状态中取 max,特殊地,持棋者可以一步不走,所以 fu 也可以为 0。一遍 dp 更新完 f 之后,我们按颜色分开,即一部分是对 A 有正贡献,一部分是负贡献。那么对两者分别跑背包,然后统计即可。

P6803 [CEOI2020]星际迷航

博弈论,树形 dp,矩阵优化:模拟赛时奇妙 65 分,至今不知原因。

我们发现除了第一棵树,其他树上节点我们只需要知道以它为根节点的情况,可以考虑换根 dp。我们发现一个一个必胜点向后面接了一条边什么事都无,而一个必败点往后借一条边,如果连的是必胜也无用,否则会改变某一些点的 dp 值。我们可以考虑矩阵优化。最后再做一次树形 dp 统计答案即可,细节极多。

CF1500F Cupboards Jumps

构造,普通 dp,set 的神秘操作:我们发现题目和 h 的值无关,只和 h 的差分有关,所以我们考虑对差分 di 进行 dp,直接设 fi,x 表示前 i 个数中 |di|=x 是否可行。然后我们可以发现 dp 数组的转移情况只有:以某个点为中心翻转,整个值域覆盖,删除或加入单点。我们发现单点的值不多,可以用 set 维护,对于区间翻转操作,可以用两个便捷维护,用两个值维护区间是否翻转,即区间便宜的值。每次翻转操作我们对整个区间进行翻转,这样单次复杂度 O(logn)

P6773 [NOI2020] 命运

计数树形 dp,线段树合并维护:因为所给的链都是返祖链(两个端点有祖先-后代关系的链),所以我们可以设 fu,i 表示子树 u 内,需要在深度为 i 的祖先下面之前选一个点的方案数。如果不需要再选择,那么 i=0。然后我们考虑转移,对于 u 的一个儿子 v,我们可以考虑类似树上背包的转移,假设 depu 表示节点 u 的深度,其中根节点深度为 1,那么:

fu,i=j=0depufu,i×fv,j+j=0ifu,i×fv,j+j=0i1fv,i×fu,j

前面一个 是求当前点选择的情况,后面两个是求不选择的情况。我们发现这个东西维护一个前缀和就能够快速求出。我们可以考虑线段树合并,维护区间 dp 值的和。然后合并的时候可以维护两个全局变量表示前缀 fu 的和和后缀 fv 的和。总体比较好写。

类似题目推荐:P5298 [PKUWC2018]Minimax,期望树形 dp,同样是用线段树合并维护。

P6669 [清华集训2016] 组合数问题

数位 dp,组合数:其实就是 Cij0(modk) 的个数。然后用 Lucas 定理拆开:CnmCaibi。我们发现满足条件必须要这些组合数中必须要有某个 Caibi=0,因为每个 ai,bi<k。所以要有 ai<bi。我们发现问题可以转化成两个 p 进制数 i,j,两个有上限,要求 ij,某一位 al<bl,然后跑数位 dp 就行了。

如果不会 Lucas 定理,可以看我的 数论家族学习笔记

P5468 [NOI2019] 回家路线

本题有 加强版,注意两者数据范围不同。

斜率优化,图上 dp:发现转移花里胡哨,我们如果按每个点进行 dp 的话,发现光转移的复杂度就是 O(n2) 的,根本吃不消。我们可以考虑把航班设到状态,然后把时间(航班时间)和空间(出发点和终点)放到转移条件。然后我们把转移的式子拆开,发现可以斜率优化,现在开始考虑转移条件。对于每个航班,更新时从出发点更新,完了之后插入到终点,这样就处理好了空间限制。对于时间限制,我们可以用类似扫描线的思路,把每个航班拆成两个,按时间排序。第一次扫到就更新 dp 值,第二次插入到单调队列里。

P3244 [HNOI2015]落忆枫音

普通 dp,计数:对于一个 DAG 上的统计,我们有一个结论:假设点 u 的入度是 du,那么方案数就是 du,具体就是每个点选择一个父亲。但是新加了一条边 (s,t),可能会有环,我们要从上述答案中减去环的情况。我们假设这个环上的点为 a1,a2,,ak,大小为 k,那么我们住了这个环以外的点乱选,那么要减去的值为 dudai。我们发现这个东西可以 dp,我们假设 fu 表示从 tu 的路径上,该式的值。容易得到 fu=fvdu((u,c)E),那么答案就是 dufs

SP3734 PERIODNI - Periodni

SPOJ 太奇怪了,所以直接放 luogu 链接了。

笛卡尔树,树形 dp,组合计数:我们发现,如果按照笛卡尔树的思路分割序列,对应的两个子树内是不会互相影响的。建完笛卡尔树之后,我们考虑树上背包。设 fu,i 表示子树 ui 个的方案数。然后随便用组合数统计一下就可以了。

SOJ 28.寄

树上 dp:我的理解是拆维。考虑一个高维 dp,设 fu,x,j 表示子树 u 内,最近的关键点距离点 ux,剩下 k 个点没匹配的最小贡献,这里需要统计 k 个没匹配的点到点 u 的距离。显然可以大力背包,因为 xj 都和子树的 size 有关,所以能过 O(n4) 的。我们考虑拆维,设 fu,j 表示子树 u 内,剩下 k 个点没匹配的最小贡献,gu,x 表示子树 u 内所有点匹配,最近关键点距离点 ux。然后 fu 正常更新,用 gu 来更新 fu,0gu 的更新就相当于已经知道两棵子树的 g,合并它们(两子树顶点有一条权值为 w 的边)。假设两子树根分别为 v0,v1,那么我们可以先枚举关键点 jv0,再枚举 v1 里面有多少个没匹配的点,让它们全部连向 j,这一步要用到 gv0fv1。同理,反过来,当关键点在 v1 时做相同处理。更新 g 的时间复杂度可以考虑每两个点会在它们的 lca 被统计,所以是 O(n2),更新 f 的同理。总时间复杂度 O(n2)

SOJ 30.润

ddp:先考虑朴素 dp,设 fl,r,gl,r 表示区间 [l,r] 有无进位的答案,那么如果 sl=0,那么 fl,r=2fl+1,r+work(l,r),gl,r=fl+1,r+gl+1,r+work(l,r),否则 fl,r=fl+1,r+gl+1,r+work(l,r),gl,r=2gl+1,r+work(l,r)。然后考虑 ddp,写成矩阵形式,从 r 往前转移。注意 work(k) 只和 k 的二进制位数有关,可以递推,但是要特判 k=2p 的情况。所以查询时可以先找到从右往左的第一第二个 1 的位置,分别查询即可。细节非常多,不过大样例好评!