dp 专题做题记录

dp 专题做题记录

A CF1616H Keep XOR Low

  • 模拟赛刚考过求集合最大值的版本。

由于这是一个二进制相关题目,考虑按位处理。先对 \(a_i\) 建出 0-1 Trie,然后从高往低求解:

  • 若 \(x\) 的当前位为 \(0\)。此时集合中所有数这一位上必须相同,那么也就是说我们必须在当前点的两个子树内单独选点,不能同时在两个子树内选。递归到两个子树内,将答案加起来即可。

  • 若 \(x\) 的当前位为 \(1\)。则此时如果集合中所有数这一位上相同,这个集合的所有子集都是合法答案。那么先求出在左右子树中单独选的方案数,应当是 \(2^{siz}-1\)。

    然后考虑集合中这一位有不同的情况,我们需要继续向下递归,看 \(x\) 的下一位的值是多少。令当前左右子树对应的两个点为 \(A,B\),对应的左右子树是 \(A_0,A_1\) 和 \(B_0,B_1\)。

    • 若 \(x\) 的下一位是 \(0\)。那么此时只有两种匹配是合法的:\((A_0,B_0)\) 和 \((A_1,B_1)\)。递归处理即可。
    • 若 \(x\) 的下一位是 \(1\)。那么此时左右的所有匹配都是合法的,而我们要递归求解的应当是 \((A_0,B_1)\) 和 \((A_1,B_0)\)。求出这两个的答案之后加入别的合法集合即可。

    注意这种情况下我们需要保证每次递归求解 \((A,B)\) 答案的时候 \(A,B\) 两个子树内至少都选了一个点,否则的话就会算重。

考虑复杂度,很显然上述过程相当于遍历了一遍整棵树,因此复杂度 \(O(n\log V)\),可以通过。

B CF81E Pairs

  • 基环树上 dp 模板题,评黑有点恶俗了。

考虑连边 \((i,p_i)\),可以得到一棵基环树,然后发现这个方向就没用了。接下来考虑树形 dp,设 \(f(x,0/1)\) 表示点 \(x\) 不选 / 选时子树内答案二元组的最大值。转移非常显然,记录最优转移点即可求出方案。

处理基环树也是套路的。我们考虑环上的一个点 \(x\) 以及两个相邻点 \(y,z\)。很显然 \((x,y)\) 和 \((x,z)\) 只能保留一个。枚举断掉哪条边然后 dp 即可。复杂度 \(O(n)\)。

C CF1616G Just Add an Edge

考虑加入一条边 \(x\to y\) 之后,存在哈密顿路径的条件是什么。由于原先的连边一定满足 \(a<b\),所以我们的路径一定形如:

\[1\to2\to\cdots\to y-1\Rightarrow x\to y\Rightarrow x+1\to \cdots\to n-1\to n \]

那么我们的要求是存在 \(y-1\Rightarrow x\) 和 \(y\Rightarrow x+1\) 的两条链,并且链的交集为空,并集为 \([y-1,x+1]\)。

考虑 dp,我们将上述条件改为可以从二元组 \((y-1,y)\) 走到二元组 \((x,x+1)\)。先枚举 \((y-1,y)\),然后设 \(f_{i,0/1}\) 表示能否从 \((y-1,y)\) 到达 \((i,i+1)\) 或 \((i+1,i)\)。这样上述条件就是 \([f_{x,0}=1]\)。

考虑转移,若 \(f_{i,c}=1\),下一个转移点 \(j\) 一定在 \(i\) 右侧。此时 \(i+1\) 必须要有一条每次加一的链,而 \(i\) 需要直接跳到 \(j+1\)。对每个节点 \(i\) 维护 \(R_i\) 表示从 \(i\) 出发每次加一最远能到达哪个点,那么若 \(R_{i+1}\ge j\) 且存在边 \((i,j+1)\),则可以转移到 \(f_{j,c\oplus 1}\)。直接做的复杂度是 \(O(n^2)\),关注到这个过程类似 DAG 可达性,可以通过 bitset 优化至 \(O(\frac{n^2}{w})\),不过依然无法通过。

继续观察性质。我们关注 \(1\to n\) 这条链上的一个断点 \(p\)。注意到一个事实是,\(i<p\) 无法转移到 \(j>p\) 的位置,因为此时 \(R_{i+1}\le p\),与 \(R_{i+1}\ge j\) 矛盾。因此我们无法跨过 \(p\) 转移,故 \(p\) 的左右两侧的转移是独立的。并且此时的 \(x,y\) 一定分属 \(p\) 的两侧,否则无法满足上面的条件。

考虑以 \((p,p+1)\) 为起点进行 dp,这样我们就只用做两次 dp 了。设 \(f_{i,0/1}\) 表示从 \((p,p+1)\) 能否到达 \((i,i+1)\) 或 \((i+1,i)\)。转移和上文一致。

此时合法的 \(x,y\) 的条件就比较简单了,他们需要满足 \(f_{x,0}\and f_{y-1,0}\) 或者 \(f_{x,1}\and f_{y-1,1}\),不过这样会算重两个条件都满足的,所以再减掉即可。计算这个是容易的,乘法原理即可。

然后需要注意的是,在上述计算中我们没有确定整个哈密顿路径的起点,于是我们可能直接开始构造两条链。所以新建两个虚点 \(0,n+1\) 即可。同时注意可能不存在这样的 \(p\),很显然此时 \(1\to n\) 的链是完整的,任意方案都是可行的,直接输出即可。

总时间复杂度 \(O(n+m)\),可以通过。

总结:

  1. 尝试设计一些非常规但是符合题目要求的状态。
  2. 注意看似平凡的性质(\(x,y\) 一定在断点两侧),思考能否优化。
  3. 多起点多终点的 dp 可考虑分治结构进行优化。

D AGC040E Prefix Suffix Addition

先考虑简单的情况,如果只有两个操作中的一个该怎么办。这个问题比较简单,我们每次可以去掉一个上升段,并且不可能同时去掉两个上升段。因此我们的操作次数就是上升段个数,即 \(a_i>a_{i+1}\) 的数量。

然后观察样例二,发现实际上我们将序列拆成了 0 0 1 1 2 和 2 1 1 0 0 两个部分,分别给两种操作。这启示我们可以将每个 \(a_i\) 拆成 \(p_i+q_i\),对 \(p\) 序列全部用操作 \(1\),\(q\) 序列全部用操作 \(2\)。这样问题就转化为了上面那个简单的问题。

现在考虑怎么拆分最优,很显然考虑 dp。设 \(f_{i,j}\) 表示 \(p_i=j\) 的最小花费,则有转移:

\[f_{i,j}=\min_{k=0}^{a_{i-1}}\{f_{i-1,k}+[k>j]+[a_{i-1}-k<a_i-j]\} \]

整理一下:

\[f_{i,j}=\min_{k=0}^{a_{i-1}}\{f_{i-1,k}+[j<k]+[j<a_i-a_{i-1}+k]\} \]

考虑 \(f_{i,j}\) 的性质,发现 \(f_i\) 随着 \(j\) 增加单调不增。这是因为 \(p_i\) 越大,\(q_i\) 越小,那么两个序列的操作次数很显然不会更劣。同时注意到,\(f_i\) 中的值极差不超过 \(2\),这是因为我们最多多进行两次操作。

于是我们可以对 \(f_i\) 进行分段,不妨设:

\[f_{i,j}=\begin{cases} d+2,&j\in[0,p]\\ d+1,&j\in[p+1,q]\\ d,&j\in[q+1,a_i] \end{cases} \]

那么我们可以改写一下式子:

\[f_{i,j}=\min\begin{cases} d+2+[j<0]+[j<0+a_i-a_{i-1}]\\ d+1+[j<p+1]+[j<p+1+a_i-a_{i-1}]\\ d+[j<q+1]+[j<q+1+a_i-a_{i-1}]\\ \end{cases} \]

令 \(\Delta=a_{i}-a_{i-1}\),我们分类讨论一下 \(\Delta\) 与 \(0\) 的关系,可以写出三个式子在数轴上的取值,三种取值取 \(\min\) 即可。精细讨论一下转移可以做到 \(O(1)\),于是总复杂度 \(O(n)\)。

总结:

  1. 有多种操作的时候可以考虑将贡献拆到每一种操作上,对每一种操作单独算贡献。
  2. dp 时记得观察一下数组的性质,注意分段 / 单调性 / 凸性 / 极差等信息。

E CF1007E Mini Metro

这是一个二维的问题,考虑在一维上做扫描线,从前往后一个个加入站台进行 dp。

先刻画一下这个火车的结构,这个火车会从 \(1\) 开始走到每个点结束,拉走中间的所有顾客。为了方便起见,我们添加一个 \(n+1\) 号点,令 \((a,b,c)=(\infty,0,\infty)\),这样的话我们可以强制让每一列火车都正好拉走 \(k\) 个人。

注意到当一个点是终点的时候,他前面的所有车站一定全部为空。那我们在考虑前 \(i\) 个车站的时候,可以枚举最后一次以 \(i\) 为终点的火车的时间,这样的话问题就可以被转化为两个时间限制更小的子问题,我们考虑通过这个结构 dp。

令 \(f_{i,s,0/1}\) 表示前 \(i\) 个车站要坚持到 \(s\) 时刻初,每个位置初始值是 \(0/a_i\) 所需要的最小火车数。同时令 \(g_{i,s,0/1}\) 表示前 \(i\) 个车站坚持到 \(s\) 时刻中间(拉完人后),每个位置初始值是 \(0/a_i\),清空除了 \(i\) 以外所有位置所需要的最小火车数。如果无解的话值都是 \(\infty\)。

转移的时候分类讨论有没有火车停在 \(i\),记 \(A,B\) 为 \(a,b\) 的前缀和。

  • 如果没有火车停在 \(i\)。

    这要求 \(s\) 小时内 \(i\) 车站合法,即 \(z\times a_i+s\times b_i\le c_i\)。转移为:

    \[f_{i,s,z}\leftarrow f_{i-1,s,z} \]

    而对于 \(g\) 来说,他必须要保证 \(f\) 有解才能转移。如果有解,那么我们需要花费 \(num=\left\lceil\frac{z\times A_{i-1}+s\times B_{i-1}}{k}\right\rceil\) 去清空前面的贡献。注意我们清空的时候不能影响到站台 \(i+1\),即转移时需要满足 \(k\times num\le z\times A_i+s\times B_i\)。转移为:

    \[g_{i,s,z}\leftarrow num \]

  • 如果上一次在 \(r\) 时刻停在 \(i\)。

    那么此时我们可以分成若干个部分进行转移:

    • 在 \(r\) 时刻到达 \(i\)。
    • 对于第 \(i\) 个站台,我们需要派出一些火车,使得 \(i\) 在 \(s-r\)​ 小时内不会越界。
    • 对于前 \(i-1\) 个站台,此时已经被清空,在之后的 \(s-r\) 小时内需要合法。

    对于第一步,显然当终点为 \(i\) 时,所需火车数量为 \(g_{i,r,z}\)。

    对于第二步,显然此时站台 \(i\) 的剩余人数为 \(z\times A_i+s\times B_i-k\times g_{i,r,z}\)。那么我们需要额外派出 \(x=\max(0,\left\lceil\frac{m+(s-r)\times b_i-c_i}{k}\right\rceil)\) 辆火车保证他不会超出 \(c_i\)。同时还需要判断 \(k\times x\le m\),否则我们在虚空接人。

    对于最后一步,我们对于 \(f,g\) 有不同的代价。

    • 对于 \(f\),很显然最后一步的要求就是 \(f_{i-1,s-r,0}\)。
    • 对于 \(g\),我们要求清空前 \(i-1\) 个站台。那么代价为 \(num=\left\lceil\frac{(s-r)\times B_{i-1}}{k}\right\rceil\)。与第一大类情况相似,我们也要求不能影响 \(i+1\) 号站台,所以要求 \(k\times num\le m-k\times x+(s-r)\times B_i\)。

    于是有转移:

    \[f_{i,s,z}\leftarrow g_{i,r,z}+x+f_{i-1,s-r,0}\\ \]

    \[g_{i,s,z}\leftarrow g_{i,r,z}+x+num \]

总复杂度为 \(O(nt^2)\),可以通过。

总结:

  1. 注意结构的刻画,对于操作找到一个代表的位置进行求解。

F CF1172C Nauuo and Pictures

通过一些尝试可以发现,此题的答案实际上只与他的权值有关,那么我们考虑对于每一个权值求出答案。

令 \(f_w(i,p,q)\) 表示对于一个初始权值为 \(w\) 的数。还剩 \(i\) 轮,当前 \(\sum[a_i=1] w_i=p\),\(\sum[a_i=0] w_i=q\) 的期望。

先考虑当前点 \(a=1\) 的情况,枚举第一轮的选法。

  • 如果选中 \(j\):

    \[f_w(i,p,q)=f_{w+1}(i-1,p+1,q)\times \frac{w}{p+q} \]

  • 如果选中 \(j\) 之外喜欢的:

    \[f_w(i,p,q)=f_w(i-1,p+1,q)\times \frac{p-w}{p+q} \]

  • 如果选中不喜欢的:

    \[f_w(i,p,q)=f_w(i-1,p,q-1)\times \frac{q}{p+q} \]

然后观察性质,发现 \(f_w(i,p,q)=wf_1(i,p,q)\),证明考虑归纳。

  • \(i=0\) 显然成立。

  • 当 \(0\sim i-1\) 均满足条件时,有:

    \[\begin{aligned} f_w(i,p,q) &=f_{w+1}(i-1,p+1,q)\times \frac{w}{p+q}+f_w(i-1,p+1,q)\times \frac{p-w}{p+q}+f_w(i-1,p,q-1)\times\frac{q}{p+q}\\ &=f_1(i-1,p+1,q)\times \frac{w(w+1)}{p+q}+f_1(i-1,p+1,q)\times \frac{(p-w)w}{p+q}+f_1(i-1,p,q-1)\times \frac{qw}{p+q}\\ &=f_1(i-1,p+1,q)\times \frac{(p+1)w}{p+q}+f_1(i-1,p,q-1)\times \frac{qw}{p+q}\\ &=w\times \left(f_1(i-1,p+1,q)\times \frac{p+1}{p+q}+f_1(i-1,p,q-1)\times \frac{q}{p+q}\right) \end{aligned} \]

    同时,我们发现:

    \[\begin{aligned} f_1(i,p,q)&=f_2(i-1,p+1,q)\times \frac{1}{p+q}+f_1(i-1,p+1,q)\times \frac{p-1}{p+q}+f_1(i-1,p,q-1)\times \frac{q}{p+q}\\ &=f_1(i-1,p+1,q)\times \frac{2}{p+q}+f_1(i-1,p+1,q)\times \frac{p-1}{p+q}+f_1(i-1,p,q-1)\times \frac{q}{p+q}\\ &=f_1(i-1,p+1,q)\times \frac{p+1}{p+q}+f_1(i-1,p,q-1)\times \frac{q}{p+q}\\ \end{aligned} \]

    于是结论得证。

同时很显然的是 \(i,p,q\) 我们只需要保留两个,并且完全可以全部塞到 \(m\) 范围内。也就是令 \(F(p,q)=f_1(m-p-q,P+p,Q-q)\),那么转移方程为:

\[F(p,q)=F(p+1,q)\times \frac{P+p+1}{P+Q+p-q}+F(p,q+1)\times\frac{Q-q}{P+Q+p-q} \]

偷懒的话 \(O(m^2 \log p)\) 可以递推出来,预处理也可以不过似乎没必要。

当然了我们还有一种情况是不喜欢的位置,这个转移是类似的,这里直接给出转移:

\[G(p,q)=G(p+1,q)\times \frac{P+p}{P+Q+p-q}+G(p,q+1)\times\frac{Q-q-1}{P+Q+p-q} \]

总复杂度 \(O(n+m^2\log p)\),可以通过。

总结:

  1. dp 中如果转移结构类似,只有某个值影响答案的时候,考虑能否将这个值的影响提到 dp 之外进行处理,这样可以节省复杂度。
  2. 如果 dp 维数较高,考虑在某一维上归纳证明,然后把这一维转化掉。

G P9663 [ICPC 2021 Macao R] Permutation on Tree

容易发现这个排列就是树的拓扑序,那我们先考虑朴素的树的拓扑序怎么求。令 \(g_u\) 表示 \(u\) 子树内的拓扑序数量,则有转移:

\[g_u=\prod g_v\times \frac{(siz_u-1)!}{\prod siz_v!} \]

意义比较显然。接下来我们要对排列的分数进行计数,很显然要考虑拆贡献。我们枚举两个点 \(a,b\),然后计算 \(|a-b|\) 对于答案的贡献。分类讨论一下。

  • 如果 \(a,b\) 有祖先关系。

    此时必然满足 \(a,b\) 之间有连边。不妨设 \(a\) 是 \(b\) 的祖先。那么此时我们计算出 \(a\) 子树内有多少种拓扑序能够取到 \(|a-b|\)。我们设一个函数 \(w(u,v_1,v_2\cdots)\) 表示从 \(u\) 子树内扣掉 \(v_1,v_2,\cdots\) 这些子树后的拓扑序方案数。那么我们可以得出此时方案数为:

    \[w(a,b)\times \binom{siz_a-2}{siz_b-1}\times g_b \]

  • 如果 \(a,b\) 没有祖先关系。

    我们要求 \(a,b\) 要连在一起,那么要先将 \(a,b\) 祖先全部取走。令 \(c=\text{LCA}(a,b)\)。我们先计算取完 \(a,b\) 子树的方案数,很显然是:

    \[\binom{siz_a+siz_b-2}{siz_a-1}\times g_a\times g_b \]

    然后我们要求出 \(c\) 子树内有多少种拓扑序能取到这个,考虑一个 dp,设 \(f(u,v)\) 表示取完 \(u,v\) 子树内的方案树,初值就是把上面那个式子赋给 \(f(a,b)\)。转移有两种,要么跳 \(u\) 要么跳 \(v\),以跳 \(u\) 举例,有转移:

    \[f(fa_u,v)\leftarrow f(u,v)\times w(fa_u,u)\times \binom{siz_{fa_u}+siz_v-2}{siz_u+siz_v-1} \]

    等我们将两个点都跳到 \(c\) 的儿子就可以统计贡献了,设两个点为 \(a',b'\),那么贡献为:

    \[f(a',b')\times w(c,a',b')\times \binom{siz_c-2}{siz_{a'}+siz_{b'}-1} \]

对于每个子树我们有一个初始的贡献,用最开始的那个 dp 可以很容易的推到全局。复杂度 \(O(n^4)\)。

发现复杂度瓶颈在第二种分类,观察发现这个转移结构对于所有 \(a,b\) 是固定的,并且除了初值外转移和 \(a,b\) 的值并没有关系。所以我们考虑将所有情况放到一起转移。具体的,将 \(f\) 的定义改为所有子树内的 \(a,b\) 在 \(u,v\) 处的贡献总和,转移式不变,在 \(fa_u=fa_v\) 的时候做贡献即可。转移时注意要保证合法,可以用 DFS 或拓扑排序来转移。最后 dp 一遍求出答案即可。

复杂度 \(O(n^2)\)。

总结:

  1. 如果转移式与枚举量无关时,考虑能否将所有 dp 转移合并起来优化复杂度。

H CF698C LRU

先观察题目,\(10^{100}\) 我们可以认为是无穷大。无穷次操作我们显然不能按顺序考虑,我们倒着考虑,看哪些操作对答案有贡献。我们发现,最后队列里的元素将会是从后往前选取,保留前 \(k\) 个不同的元素后得到的结果。

于是我们考虑从后往前状压 dp,设 \(f_S\) 表示当前队列中元素为 \(S\) 的概率。我们考虑新加入一个元素 \(i\) 的概率是多少。记 \(sum_S\) 表示选中 \(S\) 中的元素的概率总和。

  • 第一次选中 \(i\) 的概率为 \(p_i\)。
  • 第二次选中 \(i\) 的概率为 \(sum_S\times p_i\)。
  • 第三次选中 \(i\) 的概率为 \(sum_S^2\times p_i\)。
  • ……

然后可以得出下一次选中 \(i\) 的概率为 \(\frac{p_i}{1-sum_S}\),于是有转移:

\[f_S\times \frac{p_i}{1-sum_S}\to f_{S\cup \{i\}} \]

最后我们用元素个数为 \(k\) 的 \(S\) 更新答案即可,复杂度 \(O(n2^n)\)。注意我们的 \(k\) 要和 \(p_i\ne 0\) 的元素个数取 \(\min\)。

I CF1874E Jellyfish and Hack

我们发现这个代码就是一个快速排序的假做法,每次会按照序列中第一个数的大小将序列分成两个部分然后递归。那么显然考虑利用 dp 求解。

求解前先要发现一个事情:对于长为 \(n\) 的序列,函数的运行次数最大值是 \(m=\dfrac{n(n+1)}{2}\)。所以考虑设 \(f(i,j)\) 表示长为 \(i\) 的序列运算总次数为 \(j\) 的方案数。这样我们的总状态数就是 \(O(n^3)\) 了,然后转移方程是简单的,枚举第一个数的大小 \(k\) 即可:

\[f(i,j)=\sum_{k=1}^i \binom{i-1}{k-1}\sum_{p=0}^{j-i} f(k-1,p)\times f(i-k,j-i-p) \]

直接做是 \(O(n^6)\) 的,显然过不了。发现后面的式子非常像卷积形式,不过这样做只能优化到 \(O(n^4 \log n)\),还是过不了。这个时候我们想到只存点值,然后转移就是 \(O(n^4)\) 的了,最后我们做一下 IDFT 就可以知道系数。想法很美好但是现实很残酷,因为模数是 \(10^9+7\),不是 NTT 模数;而使用 MTT 又无法只存点值。

换一种思路,考虑 OGF,令 \(F_i=\sum f(i,j)x^j\),显然这是一个 \(m\) 次多项式,我们需要 \(m+1\) 个点值,然后拉格朗日插值之后就可以求出这个多项式了。那么此时有转移:

\[F_i=\sum_{k=1}^i \binom{i-1}{k-1}F_{k-1}\times F_{i-k}\times x^i \]

这样的话对于每个 \(x\),我们就可以做到单次 \(O(n^2)\) 转移出点值了,最后做一遍拉格朗日插值即可,总复杂度 \(O(n^4)\)。不过直接写常数很大,CF 上的 32 位评测机不太能跑的过去,所以要做一些必要的优化。一个优化是我们把转移中的组合数拆到两边:

\[\frac{F_i}{i!}=\dfrac{1}{i}\sum_{k=1}^i \frac{F_{k-1}}{(k-1)!}\times \frac{F_{i-k}}{(i-k)!}\times x^i \]

这样的话可以少算一个组合数。然后就是利用 long long 少取模,同时转移的时候可以优化掉 \(\dfrac 12\) 的常数,这样的话就可以跑过了。

posted @ 2026-11-02 11:36  UKE_Automation  阅读(3)  评论(0)    收藏  举报