省选 dp 专题 2 做题记录
省选 dp 专题 2 做题记录
A CF301E Yaroslav and Arrangements
做这道题前需要注意我们的计数对象是优秀数列而不是良好数列。不过良好数列的性质还是需要分析的,不难发现,假如现在给出一个良好序列,其最大值为 \(m\),如果要插入 \(m+1\),显然只能放在 \(m\) 的空隙中,并且每新增加一个 \(m+1\) 实际上会增加两个数(一个 \(m+1\) 和一个 \(m\))。
我们先钦定首项为 \(1\),最后只需要通过最大值就可以算出有多少不同的首项。设 \(dp(i,j,k,l)\) 表示当前最大值为 \(i\),序列总长为 \(j\),最大值有 \(k\) 个,能重排后得到 \(l\) 个良好序列的序列个数。转移时先枚举 \(i+1\) 放入 \(p\) 个,根据上面的结论,总长会变成 \(j+2p\)。考虑此时多出的方案数,实际上就是将 \(p\) 个数划分为 \(k\) 个可空集合,这个是经典插板法,方案有 \(\binom{p+k-1}{k-1}\) 种。
综上转移方程为:
复杂度是 \(O(n^5)\) 的,不过常数非常小,因此可以通过。
B CF739E Gosha is hunting
首先读完题可以直接秒出一个 \(O(n^3)\) 的 dp:设 \(dp(i,a,b)\) 表示前 \(i\) 个数用 \(a\) 个宝贝球和 \(b\) 个超级球,期望得到的宝贝个数。转移是简单的,转移的贡献分别是 \(p_i,q_i,p_i+q_i-p_iq_i\)。
考虑怎样优化。实际上读完题之后我们还可以建出一个费用流模型:建立两个点 \(A,B\) 限制两种球,连边 \((A,i,1,p_i),(B,i,1,q_i)\)。然后对于每个 \(i\) 向汇点连两条边 \((1,0)\) 和 \((1,-p_iq_i)\),这样跑最大费用最大流就是答案。由于我们知道费用流函数关于流量有凸性,所以容易想到用 wqs 二分优化上述 dp。
具体的,\(dp(i,a,b)\) 关于 \(b\) 是有凸性的,所以把 dp 中 \(b\) 那一维删掉,然后二分斜率 \(k\),每选一个超级球的时候我们就将代价额外减 \(k\),然后根据 \(dp(n,a)\) 最大值所选的超级球的个数转移二分区间即可。复杂度是 \(O(n^2\log n)\) 的。
C P9479 [NOI2023] 桂花树
这个题的部分分比较重要。先看 \(m=1\),此时这个点要么在一条边上,要么挂在一个点下面,方案数是 \(2n-1\)。然后看 \(k=0\),我们考虑从小到大插点,当我们插入一个点的时候,由于 \(k=0\),所以它不能有两个子树,那么说明它插入的方案和 \(m=1\) 是完全一致的。可以算出这一部分答案是 \(\prod\limits_{i=0}^{m-1} 2(n+i)-1\)。
到这里加上前两个包的暴力,已经获得了 \(70\) pts 的高分。考虑正解,用同样的思想去考虑。依然从小到大插点,不过此时我们的方案除了像 \(m=1\) 一样以外,由于 \(k\) 放宽了限制,所以有一些点可以有多个子树。不过在后面去考虑放置比较困难,我们可以用一个提前计算的思想。具体的,我们在 \(i\) 处做决策时,可以在任意一条边上新建一个节点,把 \(i\) 连到这个点上,此时这个点就可以放 \([i+1,i+k]\) 的任意一个节点了。
综上我们转移的时候有三种情况:
- 形如 \(m=1\) 放在一条边上或挂在一个点下面。
- 新建一个空结点,连在空结点下面。
- 占用一个合法的空结点。
由于 \(k\) 只有 \(10\),用状压维护当前 \([i-1,i-k]\) 哪些点新建的空结点还没有被占,然后直接转移即可。复杂度 \(O(Tmk2^k)\),卡卡常就能过了。
D LOJ2396「JOISC 2017 Day 3」长途巴士
首先我们先分析一下性质。由于司机一定不能下车,所以在每 \(T\) 时刻中最后一个服务站一定要把水加满到足够司机喝。那么每 \(T\) 时刻中喝水时间在到达最后一个服务站后面的人肯定是不会下车的,也就是说我们对 \(D_i\) 排序后,每 \(T\) 时刻中被赶下车的人一定是某个固定前缀 \([1,p]\) 的后缀。
有了这个性质我们就可以考虑 dp 了。对服务站 dp 显然不太可行,考虑对乘客 dp。设 \(f(i)\) 表示只考虑前 \(i\) 名乘客的总费用。第一种转移就是这个乘客一直不下车,加上他所需要的水量乘 \(W\) 即可。
第二种情况就是 \(i\) 作为一个合法的下车前缀 \(p\),他会带着一段区间一起下车。枚举区间左端点,现在只要计算出这个区间下车的贡献即可。容易发现,如果一段区间要下车,那么他肯定越早下车越优。令 \(pos_i\) 表示 \(i\) 最早的下车时间对应的装水次数,那么转移方程为:
中间的和式用前缀和可以直接做掉。然后发现这个式子是一个典型的斜率优化 dp,由于斜率 \(W\times pos_i\) 不单调所以要保留凸包然后二分求最优转移点。复杂度是 \(O(n\log n)\) 的。
E P5405 [CTS2019] 氪金手游
首先很显然的一点,题目中给出的二元组 \((u_i,v_i)\) 构成一棵树。那么我们连边 \(u_i\to v_i\),并钦定以 \(1\) 为根。发现此时树并不是完全外向的,不过我们可以先从外向的情况考虑。
假设当前遍历到 \(x\),我们希望 \(x\) 被取到的时间比 \(x\) 子树内任何一个取到的时间都早。假设 \(x\) 子树内的 \(W_i\) 之和为 \(S\),根据经典结论,此时 \(x\) 先取到的概率为 \(\tfrac{W_x}{S}\);原因在于我们可以认为只要取到 \(x\) 子树外的点就重取,而第一次要取 \(x\) 子树内的点的时候先取 \(x\) 的概率自然是上式。
于是不难想到 dp,设 \(f(x,i)\) 表示当前在 \(x\) 子树内,且 \(W_i\) 之和为 \(i\) 的合法概率。转移直接按照树上背包的过程转移即可,复杂度 \(O(n^2)\)。
现在考虑反向边的限制,一个经典的想法是考虑大力容斥。令 \(F(i)\) 表示恰有 \(i\) 条反向边不合法的概率,\(G(i)\) 表示钦定有 \(i\) 条反向边不合法的概率。根据二项式反演可得 \(ans=F(0)=\sum\limits_{i=0}^{n-1} (-1)^i G(i)\)。那么我们只要在树形 dp 的时候考虑一下这条反向边要不要钦定它不合法,如果钦定了那么就要归到 \(x\) 的子树里,同时转移要带上一个 \(-1\) 的容斥系数;否则的话就不用把它的大小加入 \(x\),直接乘概率即可。复杂度依然是 \(O(n^2)\),可以通过。
F CF1152F Neko Rules the Catniverse
\(\text{Small Version}\),\(\text{Large Version}\)
发现两个版本的区别只在于 \(n\),所以我们肯定要先从 \(n\) 下手然后优化。考虑从小往大往序列里插入数字,那么我们插入 \(i\) 的时候插入的位置实际上只有两种:数列的开头或者 \([i-1,i-m]\) 中某个数字的后面。正好我们发现 \(m\) 比较小,于是想到状压这个信息。
所以容易设计出一个 dp:设 \(dp(i,j,S)\) 表示当前插入第 \(i\) 个数后,总共插入了 \(j\) 个,当前 \([i,i-m+1]\) 的出现信息为 \(S\) 的方案数,转移有两种:
- 如果不插入 \(i\),那么 \(dp(i,j,S')\leftarrow dp(i-1,j,S)\)。
- 如果插入 \(i\),那么 \(dp(i,j,S')\leftarrow dp(i-1,j,S)\times (1+\text{btc}(S))\),其中 \(\text{btc}(S)\) 表示 \(S\) 二进制下 \(1\) 的数量。
这样做的复杂度是 \(O(nk2^m)\) 的,可以通过 F1。考虑 F2,如果我们将 \((j,S)\) 再进行一次压缩,把它们压成一个状态后,不难发现转移的过程可以用矩阵快速幂优化,然后就做完了。复杂度是 \(O((k2^m)^3\log n)\),可以通过。
G CF1608F MEX counting
考虑利用 dp 求解这个问题。先考虑状态设计,表示下标的 \(i\) 以及表示当前 \(\text{mex}\) 的 \(j\) 必然是有的,但是如果只有这两个的话我们转移到 \(\text{mex}\) 改变的时候是不够用的。考虑到在一个状态中对当前有影响的只有 \(<j\) 的数字,\(>j\) 的数字只对后面有贡献,所以利用延后计算的思想,再加上一维 \(k\) 表示 \(i\) 之前有 \(k\) 种 \(>j\) 的数字(注意这里是种类数而非个数)。
然后考虑转移,分类讨论一下即可:
-
如果当前位置 \(\text{mex}\) 不变:
此时要么我们填了一个 \([0,\text{mex}-1]\) 的数,要么填了一个 \(>\text{mex}\) 的数。后者还要分类看是前面出现过的 \(k\) 种还是新的一种,综合来看转移方程为:
\[f(i,j,k)=f(i-1,j,k)\times(j+k)+f(i-1,j,k-1) \] -
如果当前位置 \(\text{mex}\) 改变:
这表明此时我们在 \(i\) 处填了一个 \(\text{mex}\),那么显然 \(\text{mex}\) 值会加一。但是修改实际上还没有结束,因为我们可以继续在前面的 \(k\) 种数字中填 \(\text{mex}\) 使得 \(\text{mex}\) 增加 \(1\)。也就是说转移方程为:
\[f(i,j,k)=f(i-1,j-1,k)+f(i,j-1,k+1)\times (k+1) \]
此时的转移是 \(O(n^3)\) 的,无法通过。优化其实是简单的,因为根据题目中的限制,我们的第二维 \(j\) 实际上的范围只有 \(2k\),记第 \(i\) 位的 \(\text{mex}\) 的范围为 \([l_i,r_i]\)。
第一种转移不用变,第二种转移需要稍作修改,因为我们枚举的实际上是上一位的 \(\text{mex}\) 值,它加一以后不一定能到 \(l_i\),如果到不了 \(l_i\) 的话我们就需要用那 \(k\) 种数字去把它填到 \(l_i\),转移的时候乘一个排列数即可;如果能到的话就按照原来的方式转移即可。复杂度就优化到了 \(O(n^2 k)\),并且这个做法的常数很小,在 CF 上用 C++17 也可以通过。
H CF708E Student's Camp
首先发现每一行的消除是独立的,每一行左右两端的消除还是独立的,于是我们可以先计算出一段消掉 \(x\) 个格子的概率 \(p_x\)。这个概率是容易算出的,有:
然后考虑 dp,最直接的想法就是设 \(dp(i,l,r)\) 表示第 \(i\) 行留下的区间为 \([l,r]\) 的合法概率,显然有转移方程:
直接转移复杂度 \(O(nm^4)\)。如果用一下二维前缀和可以做到 \(O(nm^2)\),不过还是过不了。
问题的关键显然在于我们的状态太多,考虑能不能优化一下。我们把后面的和式容斥一下,变成总方案减去 \(r'< l\) 以及 \(l'>r\) 的方案就是最后的答案。我们发现这始终只和一个端点有关,那么不妨在状态中只保留一个;又因为网格是对称的,所以区间右端点在 \(i\) 处的概率之和就是左端点在 \(m-i+1\) 的概率之和。
那么我们就可以把状态改成 \(f(i,r)\) 表示第 \(i\) 行留下区间右端点为 \(r\) 的概率。那么枚举左端点,可以得到转移方程:
显然用一下前缀和优化,令 \(g(i,j)=\sum\limits_{x=1}^j f(i,x)\),则有:
然后再维护一下 \(p\) 的前缀和以及 \(p\times g\) 的前缀和即可做到 \(O(1)\) 转移,复杂度就是 \(O(nm)\) 了,可以通过。
I P3685 [CERC2016] 不可见的整数 Invisible Integers
观察到 \(n\le 10\),说明我们肯定要状压条件。于是基本的状态就是设现在往左 / 右扩展的的是哪一个条件,并且放到了第几位。不过这里的问题就是如果我们同时向两边扩展,我们实际上是很难考虑到向右和向左的条件相交这个情况的。解决方案是我们只考虑从左往右放数字,然后把向左扩展的反着放即可。
进一步可以发现,有些条件之间是有关系的。例如对于某两个条件 \(i,j\),如果 \(i\) 已经放到了第 \(x\) 位,那么此时把这一位当成 \(j\) 的第一位,把 \(j\) 放完后就可以同时满足两个条件,我们称 \(j\) 可以接在 \(i\) 的第 \(x\) 位后面。这告诉我们转移的时候是可以更换条件的。
那么状态就是 \(dp(S,i,j,k,l)\) 表示当前已经考虑到的集合为 \(S\);当前向右的条件是 \(i\),且放完 \(1\sim j-1\) 位、准备放第 \(j\) 位;当前向左的条件是 \(k\),且放完 \(len_k\sim l+1\) 位、准备放第 \(l\) 位,还需要的最少步数。转移有三种:
- 把向右的条件 \(i\) 换成 \(x\),需要满足 \(x\) 可以接在 \(i\) 的第 \(j\) 位后,转移为 \(dp(S\cup \{x\},x,1,k,l)\to dp(S,i,j,k,l)\)。
- 把向左的条件 \(k\) 换成 \(x\),需要满足 \(k\) 已经放完,并且可以接在 \(x\) 的第 \(y+1\) 位后,转移为 \(dp(S\cup\{x\},i,j,x,y)\to dp(S,i,j,k,0)\)。
- 枚举下一个要放的数 \(x\),需要满足此时这个 \(x\) 在 \(i\) 的前 \(j\) 位以及 \(k\) 的前 \(l+1\) 位出现过,并且为了更优此时一定要有一个条件的下一位是这个数,转移为 \(dp(S,i,j+[a_{i,j}=x],k,l-[a_{k,l}=x])+1\to dp(S,i,j,k,l)\)。
然后就是初值和终止状态。为了限制这两个,我们定义 \(i=0\) 表示还没有考虑向右的条件,\(k=0\) 表示向左的条件已经考虑完了。那么初值就是 \(dp(U,i,len_i+1,k,0)=0\),其中 \(U\) 是全集;而答案就是 \(dp(\varnothing,0,0,0,0)\) 或者 \(dp(\{k\},0,0,k,len_k)\)。同时为了转移到 \(k=0\),我们还要再加一个转移:
- 不再考虑向左的条件,转移为 \(dp(S,i,j,0,0)\to dp(S,i,j,k,0)\)。
那么这道题就做完了,复杂度是 \(O(2^n\times 10^2\times n^3)\) 的,可以通过。由于转移顺序不是特别清晰所以可以用记忆化搜索实现。
J GYM103447A [CCPC 2021 Harbin] So Many Lucky Strings
考虑回文串状态如何表示,最好想的就是从回文中心向两边扩展,于是想到利用区间 dp 求解。那么区间 dp 自然要记录左右端点,但是此时只记录左右端点显然不够,因为实际上我们拼接的时候有一边的字符串会剩下一段没有匹配上,这个也要记录到状态中。所以状态为 \(f(i,j,k)\) 表示左端点为 \(s_i\),右端点为 \(s_j\),\(s_i\) 中 \(1\sim k\) 没有匹配上的方案数;同理 \(g(i,j,k)\) 表示左端点为 \(s_i\),右端点为 \(s_j\),\(s_j\) 中 \(len_j-k+1\sim len_j\) 没有匹配上的方案数。
显然这个状态数实际上是 \(O(n\sum |S|)\) 的,这意味着我们需要用 vector 去存状态。同时为了避免重复,钦定 \(f\) 中 \(d\ge 0\) 而 \(g\) 中 \(d>0\)。
先看初值,有两种可能,一种是一个字符串跨过回文中心,那么初值就是 \(f(i,i,*)=g(i,i,*)=1\);而且这里面还要再分是以某个字符为中心还是某个间隔为中心。第二种就是没有字符串跨过回文中心,那么直接枚举回文中心两边的字符串即可。
接下来看转移,显然枚举 \(i,j,d\),然后分类讨论看当前新加入的字符串是 \(s_i\) 还是 \(s_j\),实际上只用比对两者当前加入的长度即可。转移的时候相当于固定了一边的字符串取了一段,然后另一边可以任意取,即需要求出形如 \(\sum\limits_{k<j} f(i,k,d)\) 或者 \(\sum\limits_{k>i} g(k,j,d)\) 的值,做一下前缀和优化就可以 \(O(1)\) 转移了。注意这里有一个细节,如果此时两边加入的字符串长度相等,我们要用 \(g\) 来转移。
上面有很多地方都需要判断两个字符串是否相等,这个直接对每个串做两次哈希就行,不是难点。最后的答案显然就是 \(\sum f(i,j,0)\)。那么总复杂度就是 \(O(n\sum |S|)\) 的,可以通过。
K CF1815E Bosco and Particle
首先每个对撞器可以只保留最小周期,原因显然。可以用 KMP 或者哈希找出来。
考虑从前往后对每个对撞器计数。对于一个对撞器 \(i\),我们先单独把它拿出来构成一个对撞机 \(0,i,n+1\),然后跑一遍看 \(0\to i\) 和 \(i\to n+1\) 各经过多少次,记为 \(a_i,b_i\)。再进一步假设 \(f_i\) 表示在原对撞机中 \(i\to i+1\) 走的次数,则此时非常容易看出 \(f_{i-1}:f_i=a_i:b_i\),且最后答案就是 \(2\sum f_i\)。
那么此时有 \(f_i=f_{i-1}\times\tfrac{b_i}{a_i}\),同时还需要有 \(\tfrac{f_{i-1}}{a_i}\) 为整数。一个简单的做法是直接维护当前的 \(f_{i-1}\),为了满足后一个要求,显然 \(f_{i-1}\) 要更新为 \(\text{lcm}(f_{i-1},a_i)\),然后再根据这个算出 \(f_i\) 的值即可。这样的话复杂度不是很优,我们有更好的实现方式。
根据第一个式子我们知道 \(f_{i-1}\) 实际上等于 \(f_0\times \tfrac{b_1\cdots b_{i-1}}{a_1\cdots a_{i-1}}\),然后我们对于每个 \(i\) 又要求 \(\tfrac{f_{i-1}}{a_i}\) 是整数,综合起来看就是说 \(f_0\times \tfrac{b_1\cdots b_{i-1}}{a_1\cdots a_i}\) 是整数。我们动态维护后面这个分式中每一个质因子的次数,对于一个质数 \(p\),如果其在 \(i\) 处的次数为 \(\alpha_i\),那么为了满足这些数恒为整数,\(f_0\) 中含这个质数的次数应该至少是 \(\max(0,\max(-\alpha_i))\)。于是我们就可以直接求出 \(f_0\) 的最小值,再递推一下就可以求出所有的 \(f_i\) 了。
这样做的话每次我们只用对 \(a_i,b_{i-1}\) 分解质因数,复杂度瓶颈就只在这了。这一部分复杂度视实现而定,可能带个根号或者老哥,反正都能过。
L CF1621G Weighted Increasing Subsequences
首先可以想到的是直接对序列计数太困难,所以考虑对每个数单独计算其对答案的贡献。
考虑一个位置 \(x\) 在什么时候有贡献,显然是一个上升子序列包含它且结尾后面有 \(>a_x\) 的数才行。那么我们令 \(ps_x\) 表示最后一个 \(>a_x\) 的数,这个可以简单二分找到,则这个包含 \(x\) 的上升子序列的结尾必须要 \(<ps_x\) 才行。
此时直接计数还是比较困难。再转化一下,考虑正难则反,我们用所有包含 \(x\) 的上升子序列减去结尾 \(\ge ps_x\) 的上升子序列。前者用树状数组预处理一下前缀和后缀子序列个数即可;后面这个看上去没有变简单多少,实际上我们思考一下会发现,\(ps_x\) 已经是最后一个 \(>a_x\) 的数了,所以结尾不可能比它还大,所以实际上后面计算的就是结尾为 \(ps_x\) 的子序列个数。
那么现在我们对于每个 \(x\) 需要求出包含它的以 \(ps_x\) 为结尾的上升子序列个数,实际上只要求出以 \(x\) 为开头、\(ps_x\) 为结尾的上升子序列个数即可。这个好像还是很难求,不过观察一下可以发现,此时以 \(x\) 为开头的子序列中的所有数的 \(ps\) 一定也是 \(ps_x\),那么我们把所有数按照其 \(ps\) 分成若干类,在每一类里面再跑一遍 dp 即可。如此我们就可以在 \(O(n\log n)\) 的复杂度内解决这个问题。

浙公网安备 33010602011771号