2025 提高刷题 - DP
A. CF1970G2 Min-Fund Prison (Medium)
本应是典题,愣是瞪了良久。
显然对于加边这个操作只需用并查集维护连通块,最后加上连通块的个数减一条边即可。所以只需考虑 \(x^2+y^2\) 最小即可。显然,在 \(x+y=n\) 的情况下,\(x,y\) 越接近肯定是越好的。所以我们考虑怎么删边,删边分两种情况,要么删原图上的割边,要么删掉自己加的边。对于割边,因为 \(n\) 很小,所以我们直接暴力枚举每次 ban 掉的边,DFS 找出 ban 掉一条边后两个子图的大小(前提是这条边是割边,才能分成两个子图)。对于加边,显然最后分成的两个子图就是 \(\text{siz}_i\) 和 \(n-\text{siz}_i\),其中 \(\text{siz}_i\) 就是 \(i\) 所在连通块的大小。
然后跑一个可行性背包,转移出其中一个连通块能否通过删割边/删加边使得大小为 \(k\)。转移时只转移每个连通块,分两种情况讨论,要么是删掉加边,此时 \(f_j\gets f_{j-\text{siz}_i}\),要么是删掉割边,此时我们从上一步处理出来的子图大小里转移,从 \(f\) 向 \(g\) 转移即可。
最后只需判断是否存在一个大小使得 \(f_i=1\lor g_i=1\) 即可。
B. CF1917F Construct Tree
首先将 \(l\) 从小到大排序。
- 性质 1:如果 \(l_n+l_{n-1}>d\),也就是序列中最大的两个值的和大于 \(d\),必定无解;
- 性质 2:如果存在 \(l\) 的一个子集 \(S\),使得 \(S\) 的和为 \(d\),且 \(S\) 包含 \(l_n\),必定有解;
- 性质 3:如果存在 \(l\) 的一个划分 \(S_1,S_2\) 使得这两个子集的和都不小于 \(l_n\),且 \(S_1+S_2\) 的和恰好为 \(d\),则一定有解。
性质 1 直接排序判定,性质 2 可以跑一个可行性背包 \(O(nd)\) 判定,性质 3 也可以通过一个背包 \(O(nd^2)\) 判定。加上 \(\tt bitset\) 优化,复杂度做到 \(O(\frac{nd^2}w)\),可以通过。第一个 DP 状态的设计是平凡的,第二个实际上就是多套了一层,令 \(f_{i,j,k}\) 表示枚举到 \(l_i\),\(S_1\) 的大小为 \(j\),\(S_2\) 的大小为 \(k\) 的方案数,第一维显然可以省略,当前的 \(l_i\) 显然要么是分给 \(S_1\),要么是分给 \(S_2\),两种情况都一转移即可。最后的判定当然是 \(\exists i\in[l_n,d-l_n]\) 使得 \(f_{i,d-i}=1\)。
可行性背包套 \(\tt bitset\) 的套路算是积累了。不过这题貌似三个性质才是难点罢。
C. CF1914G2 Light Bulbs (Hard Version)
只能说是道治你代码能力的好题,如果你用线段树优化建图 + Tarjan + 拓扑排序的话。
官解的什么异或哈希是肯定不会的,我们考虑思路更为亲民的图论做法。其实这题长得就很像图论,我们把一种颜色向中间区间的所有颜色连边,最后就形成了若干个连通图。给每个连通图跑 Tarjan,就得到若干个 DAG,最后只需统计每个 DAG 的入度为 \(0\) 的点的数量和大小的乘积即可。
时间复杂度瓶颈在于连边,考虑线段树优化建图。虽然只是套个板子上去,但这样做又会带来新的问题,因为线段树优化建图会产生许多不必要的节点,并把整张图连成一个连通图。所以我们不能再判断连通块了,并且在 Tarjan 的时候只能对有用的点计入 \(\text{siz}\)。跑完 Tarjan 后,应该建张新图,然后跑拓扑排序,对入度为 \(0\) 的点打上标记,最后答案就是这些点的数量之和以及 \(\text{siz}\) 的乘积。
最后:但凡涉及线段树优化建图,数组大小一定开 8 倍!
D. CF1906H Twin Friends
DP 是很好看出的,但是设不出来状态啊。
遇到这种看起来很假大空的计数题,一般的想法是简化。比如这题中易得的一点是 \(A\) 的顺序其实无关紧要,所以 \(A\) 可以固定着求,最后乘上 \(A\) 的排列数即可,也就是 \(\dfrac{n!}{\prod\text{cnt}(\texttt a\sim\texttt z)!}\),其中 \(\text{cnt}\) 是某个字母在 \(A\) 中出现的次数。接下来考虑怎么设计状态。
这个状态还是很迷的,设 \(f_{i,j}\) 表示放置到第 \(i\) 个字母,其中为了匹配第 \(i\) 个字母放置了 \(j\) 个字母 \(i\) 的下一位字母的方案数。转移方程是
解释一下这个方程,因为有当前字母 \(a_i\) 要用 \(j\) 个下一位字母,所以就要用 \(\text{cnt}(a_i)-j\) 个这一位字母,所以它的上一位 \(a_{i-1}\) 最多就只能用 \(\text{cnt}(b_i)-\big(\text{cnt}(a_i)-j\big)=\text{cnt}(b_i)-\text{cnt}(a_i)+j\) 个这一位字母。所以把满足条件的 \(f\) 数组求和,再乘上从总共的 \(\text{cnt}(a_i)\) 个字母中挑出 \(j\) 个字母匹配下一位字母的方案数。这就是这个转移方程。
最后的答案显然是 \(f_{26,0}\) 乘上文所说的 \(A\) 的排列数,因为最后一个字母没有下一位字母。
发现很容易能用前缀和优化到 \(O(26n)\)。
E. CF1905E One-X
尝试找出每个节点的贡献。易得一个节点 \(x\) 如果有贡献,必定是左右子树都至少选一个,总的方案数为 \((2^{\text{len}(l)}-1)(2^{\text{len}(r)}-1)x\)。暴力地建树统计是 \(O(n)\) 的,但是我们发现 \(\text{len}(l)\) 和 \(\text{len}(r)\) 有很多重复,于是考虑把方案数存储下来,记忆化搜索。因为树高是 \(\log\) 的,而我们每次只会对下一层累加答案,所以可以处理出每一层、每一区间长度有多少个节点,以及总的节点编号和。记为 \(\text{cnt}(x)\) 和 \(\text{val}(x)\),易得:
手模一下不难得到。
另外,还有一种做法是把每个节点的贡献转化为 \(f(x)=kx+b\),然后递推 \(k\) 和 \(b\) 并计算答案。和之前模拟赛的这个比较类似。
套路:线段树相关的统计,可以去找每个节点的贡献,尝试按层高为 log 的特性和二叉树的特性统计答案。
F. CF1874C Jellyfish and EVA
原,位于提高组数学专题 1。
显然 DP,设 \(f_i\) 为从 \(i\) 到 \(n\) 的概率,那么有
其中 \(p_{u\to v}\) 是从 \(u\) 到 \(v\) 的概率,接下来的任务是计算这个 \(p_{u\to v}\)。但我们发现它特别不好计算,发现从一个节点到另一个节点的概率仅和它的 \(f_v\) 有关,\(f_v\) 越大的点会被优先考虑。所以把 \(p_{u\to v}\) 转化为 \(g_{\text{deg}(u),i}\) 表示 \(v\) 是第 \(i\) 优的节点。考虑计算 \(g_{i,j}\)。
首先如果是最优的点肯定考虑优先选它,成功的概率为 \(1/i\)。否则一定就是最优的点没有去成,说明我们选的点不是我们期望的最优点。此时分两种情况,要么是选了比 \(j\) 更优但不是最优点的点,概率为 \((j-2)/i\),此时 \(j\) 点成为总度数 \(-2\) 时排名也靠前两位的点;要么是选了比 \(j\) 更劣的点,也是同理的。
难点全在 \(g\) 数组的预处理上。
G. CF1870E Another MEX Problem
\(\text{XOR}\) 套 \(\text{MEX}\),不好做最优化 DP。但是因为 \(n\le5000\),所以异或出来的最大值不会超过 \(2n\),可以做可行性 DP。设 \(f(i,j)\) 表示考虑到前 \(i\) 位,能否使得答案为 \(j\)。则有转移方程
暴力转移是 \(O(n^3)\) 的。但是我们发现很多 \(f(i,j)\) 都是 \(0\),不能提供贡献,但我们依然要遍历它,造成了时间的浪费。如果我们能只转移有用的区间,就能降低时间复杂度。
考虑到如果一个区间 \([l,r]\) 的子区间 \([l',r']\) 满足 \([l',r']\subsetneq[l,r]\),但 \(\operatorname{MEX}(l',r')=\operatorname{MEX}(l,r)\),那么 \([l',r']\) 这个区间完全能代替 \([l,r]\) 转移,因为它的区间长度更短,更容易满足不重合的要求。所以我们只需把所有的 \([l',r']\) 抓出来转移,只要满足 \([l',r']\) 中不存在任意一个子区间和它的 \(\text{MEX}\) 长度相同即可。事实上,这样的区间数量上界是 \(2n\)。
所以我们只需处理出所有这样的区间,转移时抓出来转移即可,复杂度 \(O(n^2)\)。
H. CF1868C Travel Plan
不愧是 CF 上为数不多的中国人出的题,上一道叫 Jellyfish and EVA。但那道题远不及这道题。
根据我们以前的经验,我们尝试统计每个节点作为最大值的贡献。设一条路径上所有数小于等于 \(x\) 时的方案数为 \(F(x)\),则显然 \(x\) 作为最大值的方案数为 \(F(x)-F(x-1)\),题目要求的答案就是
考虑怎么求出 \(F(x)\)。观察到 \(n\le10^{18}\) 而 \(m\le10^5\),说明复杂度应该是 \(O(m\log n)\) 左右。考虑 DP。设 \(f_{s,k}\) 表示子树大小为 \(s\)、所有数小于等于 \(k\) 的路径的方案数,\(g_{s,k}\) 表示同样条件下一端是根的路径的方案数。则有
其中 \(\mathit{ls},\mathit{rs}\) 是左右子树的大小。\(g\) 的转移中要统计左子树小于等于 \(k\) 右子树随便取、右子树小于等于 \(k\) 左子树随便取、路径上只有一个根节点左右子树都随便取的方案数,再乘上根节点从 \(1\) 取到 \(k\) 的方案数;\(f\) 的转移中要统计以根为一端、路径一端在左子树一端在右子树、全在左子树、全在右子树的方案数。
仔细理解这个转移方程之后,我们发现时间可以记忆化搜索,空间会爆炸。但因为它是一棵完全二叉树,所以不同 \(\mathit{ls},\mathit{rs}\) 个数是 \(O(\log n)\) 级别的,可以把原数组的定义改成大小为 \(2^s-1\) 的方案数,这样空间也是 \(O(\log n)\) 的了。
坑点主要在于如何计算左右子树大小。因为是完全二叉树,所以层高差不超过 \(1\),可以先把相同的部分平均分给左右子树,再分剩下的。尽量使用系统自带的 __lg()
和 __builtin_popcountll()
函数,但需要注意判断有没有负数这类的边界情况。
恶心!
I. CF1866M Mighty Rock Tower
原,傻逼推式子题。就它这个状态设计就很神奇,设 \(\mathit{dp}_i\) 为从第 \(i-1\) 个到第 \(i\) 个的期望步数,得
中间的化简过程就不敲了,可以看看这篇里写的,非常详细,很考验推式子功底。
最后得到的式子暴力做是 \(O(n^2)\) 的。看到和式我们很想前缀和优化,但式子里掺杂了一个 \(p_i^j\) 使我们不好处理。但观察到 \(p_i\) 的取值范围只有 \(0\sim99\),所以我们可以针对每一种 \(p\) 都开一个前缀和,然后这题就完了。
J. P10599 BZOJ2164 采矿
题目理解难度远大于题目难度好吧。
路径查询?子树查询?树剖稳了。
至于查询一个子树的最优安排,直接一个树上背包(牢记 \(i,j\) 要从 \(\bf0\) 开始枚举)。
然后路径查询就查询一条路径上的 DP 最大值即可,结合子树查询的 DP 值再进行一次 DP。
没什么可说的了。