DP 做题记录
CF1392H ZS Shuffle Cards
首先我们观察整个游戏过程,发现游戏可以分为若干“轮”,即每次从牌堆刚被打乱到第一次抽到 joker 的时间
无聊推式子做法
直接对着这个结构 dp
设 \(E(r)\) 为在某一轮刚开始的时候,集合 \(S\) 还差 \(r\) 个数时游戏结束的期望秒数,则
牌堆的总方案数为 \(\frac{(n + m)!}{m!}\),利用插板法,可以得到满足剩下 \(i\) 种数不在 \(S\) 中的方案数为 \(r! \binom{i + m - 1}{i} \frac{(n + m)!}{(m + r)!}\)
说明
考虑先将不在 \(S\) 中的 \(r\) 个数排成一行,那么牌堆里第一个 joker 一定恰好在第 \(r - i\) 张牌之后的位置,然后插入剩下的 \(m - 1\) 张 joker,有 \(\binom{i + m - 1}{i}\) 种方案,最后将剩下的 \(n - r\) 个数插入到整个序列中的任意位置,方案数为 \(A_{n + m}^{n - r} = \frac{(n + m)!}{(m + r)!}\)
由于组合数仅与 \(i\) 有关,可以前缀和优化
对于第二项,其为定值,只需枚举牌堆第一张 joker 的位置即可,为 \(\sum_{p=1}^{n+1} p\binom{n + m - p}{m - 1}n!\)
注意递推式求和上界为 \(r\),即有可能该轮一张也不取,所以需要化简递推式,然后递推即可
高妙做法
期望时间即为期望翻的牌数,考虑 \(E(整个游戏翻的牌数)\) 是什么,有
考虑到每轮翻开牌的期望是相等的,设游戏轮数为 \(X\),有
\(E(单轮牌数)\) 即为
具体证明可以参阅《具体数学》,主要是利用 \(t\binom{t-1}{m} = (m + 1)\binom{t}{m + 1}\)
当然也有根据组合意义更简单的证明,考虑 \(E(单轮牌数)\) 即为 \(E(前面没有\ joker\ 的牌的数量)\)
考虑一张数字牌与其他 \(m\) 张 joker 的相对位置,如果它在所有 joker 前面,那它对答案的贡献为 \(1\),在所有 \((m+1)\) 种相对位置中,有 \(1\) 种满足条件,所以概率为 \(\frac{1}{m + 1}\),那么总期望就是 \(n\) 张数字牌的概率和加一张 joker,即 \(E(单轮牌数) = \frac{n}{m + 1} + 1\)
接着我们考虑 \(E(X)\),有
考虑 \(E(当前\ |S| = i,得到一张新牌的期望轮数)\),由于有 \(n - i\) 张牌已经被选过,所以可以被忽略,每次如果抽到,就自动跳过,在抽一张牌,可以认为是在删去这 \(n - i\) 张牌的牌堆(称为有效牌堆)上抽牌,称除这 \(n - i\) 张牌之外的牌为有效牌,那么我们抽到的第一张有效牌是数字牌的概率是 \(p=\frac{i}{m + i}\),亦即有效牌堆顶是数字牌的概率
因此,这可以看成一种几何分布,抽到数字牌视为成功,那么成功的期望轮数即为 \(\frac{1}{p} = \frac{m + i}{i}\)
由于抽到新牌时,那一轮并没有结束,所以我们的期望实际上是期望失败次数,即 \(E = \frac{1}{p} - 1 = \frac{m}{i}\)
所以,
所以最终答案就是
\(H_n\) 是第 \(n\) 项调和级数
CF1626F A Random Code Problem
学会回溯
首先我们看到题目,看到期望,考虑拆到数组每个位置上,于是由于数值过大,我们可以将一个数视为一个长为 \(1 \sim k - 1\) 的数组 \(a\),\(a_i = x \bmod i\),那么我们可以在这个数组上 dp,设状态为 \(f(i, v)\),表示循环进行到第 \(i\) 轮,当前数减掉的值为 \(v\) 时剩下循环中所期望贡献的值
由于 \(a_i < i\),所以 \(v\) 是 \(O(k^2)\) 的,整个 dp 是 \(O(k^3)\),对每个数跑一遍,\(O(nk^3)\)
我们发现如果两个数模 \(1 \sim k - 1\) 分别相等,那么这两个数可以视作等价的,亦即 \(a \equiv b \pmod S\),其中 \(S=\mathrm{lcm}(1, 2, \cdots, 16)\),那么状态数锐减,并且可以解决上面数值过大的问题,进而我们可以直接将数值放到状态里,去掉 \(v\)(也可理解为原来状态其实有三个维度,现在减少值与原值可以合并),进而达到 \(O(Sk)\) 的复杂度
不会做时要学会回溯,查看自己新发现的结果对解决以前的问题有无帮助,或者换新思路
CF1608F MEX Counting
好题,贡献延后计算
所谓贡献延后计算,就是在 dp 到某个位置时,由于这个位置填的内容会对后面产生影响,就先把这个位置空着,只记下来有这么一个空位,等到后面能计算时,再通过组合数等方式计算贡献
对于这个题,我们发现如果所填的数大于当前 mex,虽然现在不会产生贡献,但是会对后续 dp 产生影响,于是就可以在状态里钦定前面有多少个位置比当前 mex 大
具体的,设 \(f_{i, M, j}\) 表示现在考虑了前 \(i\) 位,mex 为 \(M\), 第 \(1 \sim i\) 位中有 \(j\) 个位置大于 \(M\)
讨论当前位置填什么数,
但是第三种转移时间是 \(O(nk)\) 的,无法承受,考虑变更状态,将 \(j\) 设为前面有多少种数大于 \(M\)
这个时候我们就需要在转移系数上做些手脚,对于 \(a_i > M\) 的情况,我们讨论 \(a_i\) 是否出现过
假设 \(a_i\) 是那 \(j\) 种数之一,那么 \(a_i\) 有 \(j\) 种选择,系数为 \(j\),
否则,转移系数为 \(1\)
考虑这样设的组合意义,我们确定了各个位置之间的相等关系,但是并没有确定各个数具体是什么,就相当于确定了一些联通块,但是没有给他们标号,从而我们在后面转移时对全局状态有清晰的把握
于是,新的转移方程为
对于最后一种情况,发现转移状态的 \(M, j\) 两维和固定,可以维护和转移
由于每个位置的 mex \(M_i \in [b_i - k, b_i + k]\),状态量是 \(O(k)\) 的,所以总复杂度是 \(O(n^2k)\)
注意由于需要滚动数组,且不能每轮清空,所以要注意垃圾数据
P5999 [CEOI 2016] kangaroo
连续段 dp 例题
对排列计数,并且对相邻元素的值有约束,我们考虑连续段 dp
从小往大插入,考虑连续段 dp 的三种转移,新段,追加,合并
我们发现,这题不能在段两边追加
由于我们是从小往大插入,如果在段一端追加,那么后续对这个段就不能再追加新的,也不能再合并,否则必然会形成一段长度至少为 \(3\) 的递增或递减序列
那么我们的状态就呼之欲出了,设 \(f_{i, j}\) 为已经插入了 \(i\) 个数,当前有 \(j\) 个连续段的方案数,则对于 \(i < s \land i < t\),有
对于 \(i = s \lor i = t\),我们只能将其插在首尾,所以转移系数为 \(1\),
注意这时“合并”相当于把开头/结尾段与一个虚段合并,不会减少段数
对于在 \(s, t\) 之间的 \(i\),首尾之中有一处不能新增段,
对于 \(i > s \land i > t\),
最后答案为 \(f_{n, 1}\),复杂度 \(O(n^2)\)
CF1515E Phoenix and Computers
\(O(n^2)\) 的简单做法
这题连续段的标志更加明显,题目本身就是用连续段刻画的,所以我们考虑连续段 dp
首先我们考虑设状态为打开了 \(i\) 台电脑,有 \(j\) 个连续段的方案数,但是我们发现自动开机的电脑不太好处理,因为只要有能自动开机的电脑就要开机,但是我们不能确定两个连续段之间的距离,所以不太好处理(实际上也能处理,但是我没想到)
考虑调整状态,让所有自动开机的电脑都放到最后考虑,即我们不合并,保证最后每个段之间间隔都是 \(1\),这就相当于保证了自动开机机制
设 \(f_{i, j}\) 表示手动开了 \(i\) 台电脑,有 \(j\) 个连续段的方案数,则最后答案为 \(\sum_{i=0}^n f_{i, n + 1 - i}\)
至于正确性,
这个过程实际上是构建了一棵以开机时间为权值,电脑编号为键值的笛卡尔树有序森林,而每棵树中的点在位置上是连续的,从而与开机方案构成双射
本题也有其他做法,也可以考虑将自动开机机制直接放到转移里,设状态为开机 \(i\) 个,有 \(j\) 个连续段
追加有两种方式:1. 紧贴段端点 2. 与段端点差 \(1\),中间那个自动开启
合并也有两种:1. 两个段之间隔 \(3\) 个,开中间那个 2. 两段之间隔两个,开任意一个
P9197 [JOI Open 2016] 摩天大楼 / Skyscraper
学到了一些 Trick
考虑从小往大 dp,从而去掉绝对值,发现贡献可以提前计算,边界处需要特殊处理,设状态为 \(f_{i, j, S, d}\),表示现在从小到大考虑了前 \(i\) 个数,形成 \(j\) 个连续段,贡献为 \(S\),整个排列的首尾端点确定了 \(d \in \{0, 1, 2\}\) 个
-
新开一段:这个新点两端一定会插入新的比它大的点(整个排列首尾端点除外),所以它一定会被两边的数各减去一次,贡献为 \(-2a_i\)
-
接到段首尾:当前数一定比相邻数大,但后续还会在它另一侧加更大的数(整个排列首尾端点除外),贡献抵消,为 \(0\)
-
合并两段:当前数比相邻两数都大,并且以后不会再向两边加数,贡献为 \(2a_i\)
还需要分讨一些和首尾有关的细节,此处略
考虑时间复杂度,由于 \(S\) 这一维可增可减,可正可负,所以范围最差是 \(O(nL)\) 的,复杂度最差也就是 \(O(n^3 L)\) 的
不过实测把 \(S\) 一维开到 \(10^4\) 也能水过
我们考虑如何让 \(S\) 不降,这样我们就可以舍弃 \(S > L\) 的部分
对于绝对值,其几何意义为数轴上两点距离,我们考虑将它拆到每条单位线段上统计贡献,即 \(a\) 中相邻两项的差值,这个 Trick 也叫微元贡献法
考虑对于 \(i < j\),\(a_j - a_i = \sum_{k=i+1}^j a_k - a_{k-1}\),那么对于一个 \(a_k - a_{k - 1}\) 它的贡献次数就是排列 \(f\) 中满足 \(i < k \le j\) 的相邻 \((i, j)\) 对数
把这个用到 dp 上,插入 \(a_i\) 时考虑 \(a_i - a_{i - 1}\) 的贡献,每个连续段的端点(首尾除外)都将会在以后插入更大元素,且满足当前端点 \(< i\),所以贡献次数就是 \(2j - d\)
这样,每次转移令 \(S^\prime = S + (a_i - a_{i - 1})\times (2j-d)\),就能保证 \(S\) 单增,从而将状态量变为 \(O(L)\)
其实也有其中基于坐标系的几何理解,更加清晰易懂,参见洛谷第一篇题解
我们直接以值为纵坐标,排列 \(f\) 中下标为横坐标,把 \(a\) 拍到坐标系上,那么原式就是一条折线的长度,自下而上扫描维护即可 %%%
P7967 [COCI 2021/2022 #2] Magneti
类似的套路,我看到题想到直接在转移时确定磁铁间的精确距离,每个磁铁,可以视为一个长度为 \(2r_i\),在中点处有一个核心的长条,长条可以重叠但不能盖住核心,于是按 \(r_i\) 从大往小 dp,这样每次转移的位置可能情况只与 \(r_i\) 有关,可以做到 \(O(n)\) 转移,上点手法也许能 \(O(1)\),状态量是 \(O(n^2l)\) 的,但是这样还要处理端点等 case,难写还慢
实际上可以先不管精确距离,先让磁铁尽可能紧凑排列,最后再把剩余空位插入进去,写起来更简便,每个点不用视为长条,也不用再考虑端点情况
CF1696H. Maximum Product?
...
首先刻画 \(f(S)\),我们分开考虑正数和负数(先不考虑 \(0\)),记为 \(S^+= \{x | x \in S \land x > 0\}\) 与 \(S^-= \{x | x \in S \land x < 0\}\)
首先考虑 \(f(S) < 0\) 的情况,发现一定是 \(|S| = k\) 或 \(S\) 中全是负数且 \(k\) 是奇数,对这两种 case 单独做即可,其中第二种 case 需要我们求绝对值最小的 \(k\) 个数之积
接着考虑 \(f(S) > 0\)
将 \(S^+\) 与 \(S^-\) 分别按绝对值排序,得到数列 \(S^+,S^-\),那么 \(f(S)\) 一定来源于 \(S^+\) 和 \(S^-\) 的两段前缀,设为 \(X^+, X^-\),那么 \(|X^-|\) 必为偶数
通过调整,我们发现最优解 \(X^+,X^-\) 应满足如下条件:所选正(负)数绝对值最小的两个之积,大于没选的负(正)数中绝对值最大两数之积,当然如果没选的数少于两个也是最优解
这个是充要的,如果我们进一步规定正数一边可大于等于负数一边,但负数只能严格大于正数一边,也就是说尽可能往正数一边调整,则最优解方案就是唯一的,然后我们就可以愉快的拆贡献了
这样,我们有 \(8\) 个关键点
接着,通过分别在 \(A^+, A^-\) 枚举两个点,代表,\(X^+, X^-\) 中绝对值最小的四个数,另外 \(4\) 个关键点提前计算,我们就可以得到一个 \(O(n^4)\) 做法
哈哈
然后我就不会了,开始疯狂推式子尝试打包计算,发现做不了,然后整个人非常红温于是去打 ymq
其实这个方法是可以做的,我们正难则反,考虑容斥,先算出所有可能的 \(X^+, X^-\) 贡献,再将不符合最优解条件的减掉
然后我们发现,如果不满足条件,那么正数,负数之中只会有一侧不满足条件,这是极好的性质,这样我们只用考虑一侧即可,变成 \(4\) 个关键点,\(X^+, X^-\) 各两个
这里如果我们考虑一边的点对,在另一个数列上按单点计数,反而会更难做,因为这样很不对称,考虑的东西类型不一样,不太好优化,所以应该对称地考虑 \(A^+\) 与 \(A^-\) 上的两对点
考虑 \((a, b)\) 与 \((c, d)\),若 \(A^-_aA^-_b > A^+_cA^+_d\),那么应该将 \(A^+_c, A^+_d\) 替换为 \(A^-_a, A^-_b\),需要减去的贡献为:
其中 \((a, b)\) 与 \((c, d)\) 的贡献是可以拆开的,所以可以把所有点对排序,计算点对之间贡献,利用双指针,并记录计算结果,计算点对 \((x, y)\) 的贡献时只需枚举 \(t\),时间复杂度 \(O(n^2\log n + n^3)\)
最终复杂度是 \(O(n^3)\) 的
细节:
- 处理好点对之间的优先级,保证最优解唯一
- 对于 \(f(S) < 0\) 的 case,\(|S| = k\) 且 \(S\) 中全为负数,且 \(k\) 是奇数的情况会被算重,注意算 \(S\) 中全为负数 case 时去掉这一情况
CF1844H Multiple of Three Cycles
厉害题
给 \(p_i\) 赋值相当于连一条 \(i \rightarrow p_i\) 的边,要求连出来的图中所有的环长均为 \(3\) 的倍数
考虑一个没有填完的排列 \(p\),这时我们会有若干条链以及若干环,如果已经连成的环中存在长度不能被 \(3\) 整除的环,无解,否则这些环对后续答案没有贡献,可以忽略
\(\bmod 3\) 考虑,则链可按长度分为 \(0, 1, 2\) 三种,各自的数量记为 \(a, b, c\),设 \(f(a, b, c)\) 为方案数
直接选出来若干个元素构成环转移是困难的,我们考虑每次连接两条链,得到新的局面,然后发现我胡不明白转移系数,索兴随便从剩余链中选择,这样相当于一个对于一个方案,我们通过所有的连接方式各记录了这种方案一次,也就是乘上了操作次数的阶乘,即 \((a + b + c)!\)
现在我们有了 \(O(n^3)\) 做法
我们发现长为 \(0\) 的链可以单独插入,而没有长度和的限制,所以可以最后插入,即 \(ans(a, b, c) = \frac{(a + b + c)!}{(b + c)!}ans(0, b, c)\),对于 \(f\),由于操作有序,我们还需考虑在操作序列中插入 \(f(a, b, c) = (\frac{(a + b + c)!}{(b + c)!})^2 f(0, b, c)\)
现在我们有了 \(O(n^2)\) 做法,转移是:
即,
...hyw
然后我们把状态都换成无序的,即用 \(g(b, c) = \frac{f(b, c)}{(b + c)!}\) 替换 \(f(b, c)\),得到
更难绷了,不会做了,也许应该想下只有一种链应该怎么做???设其为 \(t(x)\)
我们三个三个插入,考虑当前链与哪两个配在一起,方案数是 \(2(x-1)(x-2)\),总共有 \((x - 3) / 3\) 个位点可供插入,还可以新建一个环,由于最后我们每个方案中每个环会被算 \(3\) 次,所以总共会被算 \(3\) 的环数次方次,每新建一个环就除以 \(3\) 即可
好吧,没用,贺贺贺
原来还能这样转移???
其实你只需要按顺序考虑每条链的出边指向哪里就行了,先考虑 \(1\) 类链,再考虑 \(2\) 类链,也可以先考虑 \(2\) 类链,再考虑 \(1\) 类链,即,由于 \(b, c\) 是对称的,所以交换 \(b, c\) 得到的转移式一定也合法
那么就有,
上下两个式子可以通过交换 \(b, c\) 互相转化
我们考虑将这个 \(dp\) 式子放到平面上,发现我们在 \(g(b, c), g(b - 1, c - 1), g(b + 1, c - 2), g(b - 2, c + 1)\) 中知二推四,而每次转移后新的四个状态与原来四个状态的公共点恰有两个,所以可以 \(O(1)\) 互推,就可以做了
正推倒推都可以,代码实现需要注意边界处未定义的 \(g\) 的处理
CF1097G Vladislav and a Great Legend
套路题,贡献变量在底数上,转下降幂,即
后面的 \(\sum_{X} \binom{f(X)}{i}\) 转组合意义,每个组合数代表从 \(X\) 的虚树上选出 \(i\) 条边的方案数,用 dp 计这个东西,设状态是 \(f(u, t)\) 表示以 \(u\) 为根的所有虚树上选出 \(i\) 条关键边的方案数,容易利用背包转移,但是需要讨论清楚
下面称 \(X\) 构成的虚树上,\(\in X\) 的点为实点,\(\notin X\) 的点为虚点,
考虑 \(u\) 是实点还是虚点,如果 \(u\) 是虚点,那么如果其下只确定连一棵子树,这颗虚树是不合法的,在最后统计答案时要去掉,但是在转移时为了连接根与子树,应当保留
为了方便,设 \(g(u, t, 0), g(u, t, 1)\) 分别为 \(u\) 是虚/实点时的方案
而在一个方案中,将 \(u\) 换为实点或虚点并不影响,即 \(g(u, t, 0) = g(u, t, 1)\),所以最后应对背包得到的 \(f(u, t)\) 乘上 \(2\)
而且,虚树中叶子一定是实点,所以对于在 \(u\) 子树只有 \(u\) 一个节点的虚树,\(u\) 一定为实点,所以上面的等式有一个 corner,即 \(t = 0\) 时,集合 \({u}\) 也会被计入,这时 \(u\) 只能为实点,而对于 \(t > 0\),\({u}\) 的边数为 \(0\),不会被计入,所以有
现在我们有了 \(O(nk^2)\) 做法。不过,对于这种树上背包,似乎只要改改上下界就能神奇地去掉一个 \(k\) 来着???
考虑每次转移都是在做一个卷积,上面的做法中两个多项式长度均为 \(k\),但其实有一些项是 \(0\),因为 \(t\) 受子树大小的约束,实际上将卷积改为 \(\min\{k, cur_{u, v}\} \times \min\{k, siz_v\}\) 就能将复杂度降到 \(O(nk)\),其中 \(cur_{u, v}\) 表示节点 \(u\) 在合并到儿子 \(v\) 时合并的子树大小
这看上去很没道理,因为 \(k\) 并不大,你可能会认为大部分时候都有 \(\min = k\),所以顶多只是常数优化,但实则不然,其实这就是树上背包的形式,根据经典结论,复杂度是 \(O(n \min\{n, k\})\),具体可见这篇文章
CF1349F1 Slime and Sequences (Easy Version)
被剧透了做法qwq 不然我也做不出来
先刻画一下序列结构,设 \(l_i, r_i\) 分别为数字 \(i\) 第一次和最后一次出现的位置,那么对于所有的 \(i \ge 1\),有 \(l_i < r_{i+1}\),且若最大值为 \(m\) 则 \(1 \sim m\) 均至少出现一次,所以值域为 \([1, n] \cap \mathbb{Z}\)
好像题解里面说直接对这个 dp 能获得 \(O(n^3)\) 做法
然后我们考虑拆点贡献,把答案拆到每个位置上,但是发现好像也不是很好递推
那就打点表罢
发现输出答案之和是 \(n \times n!\),意味着合法序列数量是 \(n!\),考虑构造双射
我们考虑把原序列中相等的数映射到数轴上一段连续的整数,并且我们要可以从排列中提取出这段整数,由于前面说到 \(l_i < r_{i + 1}\),所以我们可以考虑给小的数分配小的编号,并让相同数的编号按下标递减
更形象一点,从 \(1 \sim n\) 考虑每个值 \(k\),维护一个计数器 id,倒序考虑整个序列,如果这个位置上的数等于 \(k\),那么把对应排列中这个位置设为 id,并让 id++
容易发现这样构造出来的排列中的每个 极长的数值连续的下降子序列 都对应着原序列中同一种数的所有出现位置,构成双射
发现依然不好对着这个序列 dp,因为具体是哪个数是由它对应的下降子序列的 rank 决定的,这在 dp 过程中无从得知
我们考虑这个排列的特征,对于一个极长的下降子序列,设其结尾的数为 \(x\),那么其后一定没有 \(x - 1\),也就是 \(pos_{x - 1} < pos_x\),其中 \(pos_x\) 表示 \(x\) 在排列中的位置,反过来,对于该下降子序列中的其他的数,都有 \(pos_x < pos_{x - 1}\)
那么我们可以考虑 \(pos\) 这个排列,亦即考虑构造出来的排列的逆排列,这其实类似插入 dp 依次考虑每个数位置的思路
那么发现这个排列中恰有 \(k - 1\) 个上升,其中 \(k\) 是最开始那个好的序列中的最大值,上升可以用欧拉数求
所以就是要求所有排列 按上升点分段后 第 \(x\) 段的长度之和,现在,我们可以拆贡献了,将贡献拆到每个位置上,即这个位置是否在第 \(x\) 段,也就是它前面有 \(x - 1\) 个上升的方案数,后面可以随便排,所以 \(x\) 的答案(\(x\) 在所有好序列中的出现次数)就是:
\(O(n^2)\) 预处理欧拉数,\(O(n^2)\) 计算答案
CF1517F - Reunion
min-max 容斥??? 我在胡说
考虑经典 Trick
所以我们现在要求 \(P(r_{\max} \le x)\),也就是 \(P(\forall u,\ r_u \le x)\)
感觉直接对 \(r_u\) 做不太好做,我们考虑转化一下,把 \(r_u\) 转换成另一个条件,刻画一下 \(r_{\max}\)
我们称有空的志愿者为黑点,没空的志愿者为白点,那么
\(r_u \ge x \Longleftrightarrow \text{在}\ D(u, x)\ \text{内不存在白点}\)
\(D(u, x)\) 是 \(u\) 的 \(x\) 邻域
也就是说,我们需要让所有白点的 \(x\) 邻域并起来能覆盖整棵树,求染色方案数(概率)
我们发现对于一颗子树,它所受到的覆盖来自子树内和子树外,那么我们可以分别在 dp 中开一维状态记录这两个信息
也就是 \(f(u, a, b)\) 表示所有 \(T_u\) 内 \(dis(u, v) < a\) 的点均已被覆盖(注意是严格小于,因为可能一个点也覆盖不到),这棵子树还能向外覆距离小于 \(b\) 的点
考虑合并两棵子树,就有一个转移方程(我胡出来花了半天):
是 \(O(n^4)\) 的,加上外层枚举半径,总共是 \(O(n^5)\)
上面这个做法实在是没有任何前途,我们换种状态设计方法
我们之所以需要设一维外界的覆盖半径,是因为有一部分点不被子树内的点覆盖,但是我们的状态又要求子树中点全部被覆盖
所以我们可以让统计不被覆盖满的方案,将状态修改为 \(f(u, d, k)\),表示 \(T_u\) 中最深的没被覆盖的点深度为 \(d\),还能向外延伸 \(k\) 的方案数
然后我们发现关键性质,\(d,k\) 两维不会同时有意义!
如果 \(T_u\) 已经全部被覆盖,显然 \(d\) 没有意义
否则,\(T_u\) 中未被覆盖的点一定会被另一个半径更大的(另一棵子树里的)点覆盖,而这棵子树能延伸的距离肯定 \(> k\),合并后 \(k\) 就没用了
所以可以分成两个状态,\(f(u, k)\) 表示 \(T_u\) 已经全被覆盖,能向外延伸 \(k\),\(g(u, d)\) 表示 \(T_u\) 中最深的未被覆盖的点深度为 \(d\)
合并子树转移即可,由于 \(k, d\) 受深度约束,所以这个类似树上背包,\(O(n^2)\),总复杂度 \(O(n^3)\)
CF1152F2. Neko Rules the Catniverse (Large Version)
还是回溯不够啊
\(n\) 非常大,我们考虑它大概率跟复杂度无关,可以先钦定 \(k\) 个点,最后再插板算方案数,\(k, m\) 这么小,肯定是 exp 做法吧!
根本不对......
还是按顺序考虑 \(1 \sim n\) 吧,增量插入,如果插入的点被访问,它可以插入到当前链头或者插入到某个距离它小于 \(m\) 的点后面,所以需要记录它前面 \(m\) 个点是否被访问,对每个位置,状态是 \(O(k 2^m)\) 的,然后可以矩乘优化,复杂度 \(O(k^3 8^m \log n)\)
CF1067D. Computer Game
Stop learning these useless algorithms,learn how to use binary search. ——Um_nik
考虑如果没有升级决策是怎样的,那么显然我们会一直选择期望最大的任务
现在有升级,那么一个自然的观察是:只要我们有一次升级机会,我们就选择升级后期望最大的任务升级,然后一直做这个任务
因为反正全部升级后也只会一直刷一个任务,所以只升级这一个任务就行
对期望 倒着 dp,总时间为 \(T\),设 \(E_t\) 为还剩 \(t\) 时间的期望收益,\(M = \max_{i}\{b_ip_i\}\),那么
这是一个可以斜率优化的式子,变一下形
则决策点为 \((p_i, p_i a_i)\),斜率是 \(E_t - tM\)
考察 \(E_t - tM\) 的单调性,发现它是单减的
考虑 \(\Delta(E_t - tM) = \Delta E_t - M\) 的符号
通过上面转移式可以发现,\(\Delta E_t = \max_{i=1}^n \{p_i(a_i + tM - E_t)\}\)
当 \(t=0\) 时,有 \(\Delta E_t = \max_{i=1}^n \{p_i a_i\} \le M\),考虑归纳证明 \(\Delta E_t \le M\)
当 \(t > 0\) 时,有
所以是单减的
所以我们现在可以直接维护凸包进行 dp,\(O(T)\)
但是我们发现每一段中的决策点是固定的,所以可以快速转移,而段之间的分界点也容易二分出来,可以优化
二分会多一个 \(\log\),用倍增就可以去掉
自己做的时候没想到最后可分段计算,分界点可以二分求出 该退役了
CF1603E. A Perfect Problem
CF1784E. Infinite Game
挺不错的题,没做明白
首先,题目中的极限实际上就是一个循环节中的比例,然后我们可以做一个转化,Alice 胜得分为 \(1\),Bob 胜得分为 \(-1\),那么我们只需要看总得分符号就能知道局面的类型
我们发现当前局面只有 4 种,结尾是 a, b, ab/ba 或恰好一局结束(ab, ba 算一种)
并且这几种状态之间有转移关系,构成一个 DFA,循环节就相当于这个 DFA 接受了若干个 \(s\),之后访问到了访问过的状态,所以一个自然的想法是类比 AC 自动机,记当前考虑了 \(i\) 个字符,位于 DFA 的状态 \(u\),总得分为 \(w\) 的方案数
但是由于我们是将字符串循环复制,并不是随意生成一个无限长的字符串,这样不能保证每一轮中字符串是相等的
考虑我们已经确定了字符串的一个前缀,其实我们可以维护 DFA 中每个状态接受 \(s\) 后到达的最终状态,以及这期间的得分,并把它作为边权,在 dp 状态中维护这样一个连边情况
由于我们最后只需要环上边权总和,所以我们可以考虑在状态里只记一个总和,我们可以记边的一个子集的权值和,最后 check 这个子集是否等于环,也就是在外面枚举子集,跑 \(2^4\) 轮 dp
CF1616H. Keep XOR Low
做这题时刚做完 CF2205G,所以刚开始就顺着这题的思路做了
异或的话我们先考虑 Trie,在 Trie 树上 dp
下面用 \(T_u\) 表示在 \(u\) 子树内结尾的数的集合
考虑我们现在 dp 到了 Trie 树上的节点 \(u\),对应二进制数的第 \(b\) 位,那么对于 \(T_u\) 中任意两个数异或结果的第 \(b + 1\) 位及之后全部都是 \(0\),设 \(x\) 的最高位为 \(k\),如果 \(b < k\) 直接返回 \(2^{|T_u|} - 1\) 即可,如果 \(b > k\),那么肯定不能有 \(u\) 左右两边的数同时存在,递归处理两棵子树即可
难办的是 \(b = k\) 的情况,这是如果我们只选左右两边的数,是合法的,但是如果同时选两边的数,限制依然存在,难道我们要分讨,合并左右儿子?又或者是沿用 CF2205G 的思路,标记关键点,求集合交集?
似乎都不太可做
实际上直接做就行了,设 \(f(u, v)\) 表示在 \(u, v\) 两棵子树中选满足条件的子集的方案数。
设 \(u, v\) 所对应的二进制位为 \(b\),如果 \(x\) 第 \(b\) 位为 \(1\),那么可以随便搭配,\(f(t_{u, 0}, t_{v, 1})\) 与 \(f(t_{u, 1}, t_{v, 0})\) 的第 \(b\) 位异或之后紧贴上界,需要递归处理,然后把两个答案乘起来,得到总方案数
如果 \(x\) 第 \(b\) 位是 \(0\),那么只有 \(f(t_{u, 0}, t_{v, 0})\) 与 \(f(t_{u, 1}, t_{v, 1})\) 以及只选 \(u, v\) 其中一棵子树的方案合法,递归计算
容易发现在递归过程中每个点只会被 \(u, v\) 之一遍历一次,所以复杂度就是总点数量级,即 \(O(n \log V)\)
CF1787H. Codeforces Scoreboard
题意相当于选一个排列 \(p\),使得 \(\sum_{i} \max\{a_i, b_i - k_i p_i\}\) 最大
首先考虑没有 \(a_i\) 的情况,那么显然我们按 \(k_i\) 从大往小的顺序分配 \(p_i\) 是最优的
现在考虑带上 \(a_i\),仍然考虑贪心,考虑一对 \(i, j\) 满足 \(k_i < k_j\),我们希望选择交换/不交换 \(p_i, p_j\) 来使答案更优,先假设 \(p_i < p_j\),讨论以下几种 case:
-
\(b_i - k_i p_i \ge a_i\) 且 \(b_j - k_j p_j \ge a_j\),那么这时交换后一定不劣
-
\(b_i - k_i p_i < a_i\) 那么交换一定不劣,因为 \(p_j > p_i\),所以交换后一定仍有 \(b_i - k_i p_j < a_i\),且 \(\max\{a_j, b_j - k_j p_i\} \ge \max\{a_j, b_j - k_j p_i\}\),注意这条性质与 \(k_i, k_j\) 的相对大小无关
-
\(b_i - k_i p_i \ge a_i\) 且 \(b_j - k_j p_j < a_j\),不太清楚
我们仔细观察这三种情况,第一种情况告诉我们对于 \(b_i - k_i p_i \ge a_i\) 的题目,我们可以延续原来的贪心策略
第二种情况则告诉我们,如果 \(b_i - k_i p_i < a_i\) 那么我们可以把 \(p_i\) 设为 \(n\),但是细细想来,如果有多个 \(i\) 满足 \(b_i - k_i p_i < a_i\) 又怎么办呢?那我们需要给这些 \(i\) 分配一些时间点,使得每个 \(i\) 的时间上都是 \(a_i\)
但是这就很麻烦,而且如果考虑贪心,在这些 \(i\) 之间还可能会存在交换,难以决策
但是这不是贪心题
实际上我们到这里就已经把问题刻画得足够清晰了,下面我们可以开始考虑在所有情况中枚举,取最优解,也就是说,我们可以钦定最终贡献为 \(a_i\) 的题目集合,然后在所有情况中取 max,而不必再继续讨论选那些 \(i\) 会最优,而且,假如所钦定集合中有些题目没能被分配到合法的时间,那么我们所钦定出来的解一定不会是最优解,一定会被其他方案干掉,所以不用担心
然后就变成按斜率排序,每个点选或不选,算贡献(有一道类似的题 CF573E Bear and Bowling)
设计 dp,令 \(f_i(j)\) 表示考虑到第 \(i\) 个数,共选了 \(j\) 个数的最优解
那么 \(f_i(j) = \max\{f_{i-1}(j) + a_i, f_{i - 1}(j - 1) + b_i - jk_i\}\)
注意我此处用了 \(f_i(j)\) 这一写法,将 \(f_i\) 看成 \(j\) 的函数,方便优化,相当于滚动数组,转移时直接用 ds 在数组上修改
注意到 max 里只有两项,于是我们大胆猜测转移存在分界点,即存在一个 \(t\),使得
因为 \(f_i(0)\) 只能从 \(f_{i-1}(0)\) 处转移,所以第一段应该选的是 max 中的第一项
也就是说
然后,我们可以二分找到这个分界点,对后面的区间 \([t + 1, n]\) 整体向后平移,再加上 \(b_i - jk_i\) 这个等差数列,然后单独设置 \(f_i(t)\),前面的区间不动,相当于插入了一个位置,使用块状链表可以维护,由于链表不支持随机访问,我们可以将二分改为考虑每个块,复杂度变为 \(O(\sqrt n)\)
考虑更优的解法,对上式进行变形
所以在 dp 过程中,我们其实并不关心每个位置具体值是多少,而只关心它的差分,所以我们其实可以维护差分,最后再求和,这样每次就是区间加,单点插入,用平衡树维护即可,复杂度 \(O(n \log n)\)
接下来我们证明上面的结论,即
对于每个 \(i\),\(\Delta f_i(j)\) 与 \(-k_{i+1} j + b_{i+1} - a_{i+1}\) 的大小关系存在一个分界点
考虑证明原命题等价于证明 \(\Delta f_{i}(j) + k_{i+1}j - b_{i+1} + a_{i+1} = 0\) 有至多一个根
那么我们考虑它的差分,如果这个函数是不增的(只考虑单减是因为上面的分段中 \(f\) 一开始大,后来小),那么命题就成立,我们尝试一下
将原函数差分,即证 \(\Delta^2 f_{i}(j) + k_{i+1} < 0 \Longleftrightarrow \Delta^2 f_{i}(j) \le -k_{i+1}\),
考虑归纳,
对于 \(j < t\),\(f_i(j) = f_{i - 1}(j) + a_i \Longrightarrow \Delta^2 f_i(j) = \Delta^2 f_{i-1}(j) \le -k_i \le -k_{i+1}\),注意我们提前给 \(k\) 排了序
对于 \(t\),有 \(f_i(t) = f_{i-1}(t-1) + b_i - tk_i,\ f_i(t - 1) = f_{i-1}(t - 1) + a_i\),有 \(\Delta f_i(t) = b_i - a_i - tk_i\),根据 \(t\) 的定义,有 \(\Delta f_{i}(t-1) = \Delta f_{i-1}(t-1) > b_i - a_i - (t - 1)k_i\),所以 \(\Delta^2 f_i(t) = \Delta f_i(t) - \Delta f_{i}(t-1) \le -k_i \le -k_{i+1}\)
对于 \(j = t + 1\),同理
对于 \(j > t + 1\),有 \(f_{i}(j) = f_{i-1}(j - 1) + b_i - jk_i\),即 \(\Delta^2 f_{i}(j) = \Delta^2 f_{i-1}(j - 1) \le -k_i \le -k_{i+1}\)
对于 \(i = 1, 2\) 的情况,手玩可知成立,Q.E.D.
CF1774G. Segment Covering
我咋这么菜 qwq
我们考虑从右端点 \(r\) 处开始覆盖,容易发现我们选取的区间一定以 \(r\) 为结尾且不包含任何其他区间,不然取奇数与偶数的方案一定相同,会抵消掉,所以只有不相包含的区间是有用的
去掉没用的区间,剩下的区间 \(l, r\) 均递增(互不相同)
考虑递推,将奇数情况与偶数情况的递推式相减并移项,设答案为 \(\varphi(l, r)\),有这样一个式子:
其中 \(l_r\) 为以 \(r\) 为右端点的区间的左端点,对它求前缀和,得到 \(s_r - s_{l_r - 1} = 0\),即 \(s_r = s_{l_r - 1}\)
我们似乎得到了一个递推式,考虑通过它递推,注意上面的式子对 \(r\) 处没有区间也成立,我们只需要把 \(l_r\) 定义成 \(r\) 就行
所以,实际上,\(s_r = s_{p_r - 1}\),\(p_r\) 即为最大的使得 \([p_r, r]\) 中包含一条线段的数
对于边界,我们定义 \(s_L = 1, s_{<L} = 0\),最后要计算 \(s_n - s_{n-1}\),从这两个点往前跳就行了,可以倍增优化
CF1603D. Artistic Partition
其实做之前得到了一些提示
显然有一个朴素的 dp
似乎能用四边形不等式?
考虑用四边形不等式优化转移,容易发现 \(c(x, y)\) 满足四边形不等式
考虑 \(x \le y \le z \le w\),那么 \(c(x, z) + c(y, w)\) 与 \(c(x, w) + c(y, z)\) 的区别在于前者对于 \([z + 1, w]\) 内的 \(\gcd\) 限制更严,所以合法个数更小
计算 \(c(l, r)\) 肯定是重要的一步,我们先考虑怎么计算它
好像很难直接算,要不再优化上下界?
感受一下,因数这种东西似乎要在比较大的时候才能大于 \(l\)
考虑一个的因数一定小于它的一半
如果 \(\lfloor r/p \rfloor = 1\),那么只有选取 \((i, i)\) 这种数对才能满足条件,这时答案直接为区间长度
再仔细想一下,因为 \(\gcd(i, j) = \gcd(j, j - i) \ge l\) 所以 \(j - i\) 必须 \(\ge l\),也就有区间长度 \(r - l \ge l\)
但是好像还是没什么用
这是一个二维 dp,想进一步优化转移,还可以考察二维决策单调性,猜测 \(f_k(i)\) 也满足四边形不等式,打表,发现成立,可以进一步优化决策点范围
瓶颈这时在于 \(c(i, j)\) 的计算和 \(O(n^2)\) 的状态量
\(c(i, j)\) 暂时没什么想法,所以考虑优化状态,似乎没有什么更好的状态定义方式,那我们考虑是不是会有无效状态
感受一下,如果一个区间很小,那么就不会有 \(i \ne j\) 的二元组满足条件,也就上面推出来的 \(r \ge 2l\),所以如果我们分的段数 \(K\) 大于一个阈值,答案就会变成 \(n\)
考虑取第 \(i\) 个区间右端点为 \(2^i - 1\),那么我们分出了 \(\lfloor \log_2 n\rfloor + 1\) 段,所以 \(K \ge \lfloor \log_2 n\rfloor + 1\),直接输出 \(n\) 即可
这样状态量就变成 \(O(n \log n)\) 了
现在考虑怎么计算 \(c(l, r)\),可以考虑类似莫队的移动访问
考虑 \(l + 1 \rightarrow l\),由于式子的形式是 \(\sum_p f(p, r) g(p, l)\)
所以只有 \(l | p\) 的项会发生改变,变化的量为
可以整除分块做到 \(O(\sqrt{r / l})\),这样在分治的每一层,移动左端点的复杂度总和
所以我们考虑让右端点也能在 \(O(\sqrt{r / l})\) 内转移,考虑 \(r \rightarrow r - 1\) 的变化量为 \(\sum_{i=l}^{r-1} [\gcd(r, i) \ge l]\ \ + 1\)
我们考虑根据我们上面做出的观察进行一些变形
考虑如何把上界变成 \(r/l\),我们可以枚举 \(\frac{r}{\gcd(r, t)}\)!
那么
我们发现,这时求和中的限制 \(l \le k \times \frac{r}{x}\) 被自然满足了,因为 \(r / x \ge l\),我们只需满足 \(k \times \frac{r}{x} \le r\),即 \(k \le x\) 即可
那么后面就变成了 \(\varphi(x)\) !!!
所以这时我们可以直接计算原来的 \(c(l, r)\)
可做到 \(O(r/l)\),虽然不是根号,但是每一层是 \(O(n \log n)\),总复杂度 \(O(n \log^3 n)\) 可以接受
预处理所有答案后 \(O(1)\) 回答,复杂度 \(O(n \log^3 n + q)\)
哈哈,上面的分析纯乐子
还是不要机械莫反了 T^T
CF1572E. Polygon
考虑二分最小面积 \(S\),观察结构,考虑区间 dp,关键在于边 \(l - r\) 所对应于的图形面积,它可以与外界相连,称之为剩余面积
并且,我们对这一块面积的大小没有限制,如果它 \(< S\) 那我们可以把它合到其他块中,如果它 \(\ge S\) 那么我们可以把它作为一个新块,贡献 \(+1\),这样至多只会使一个包含它的区间的剩余面积 \(< S\),贡献至多 \(-1\),肯定不劣
同时,我们应该让分割块数的最大的同时让剩余面积尽量大,所以状态记成一个 pair 转移即可
CF1553I. Stairs
暴力冲过去.jpg
首先我们发现可以根据 \(a\) 数组把原排列划分成若干段,每一段都是一个极长的连续上升或下降段
然后我们考虑把这些段排列在 \([1, n]\) 的数轴上,需要满足相邻的段不能连续
首先可以考虑连续段 dp,但是是 \(O(n^2)\) 且感觉转移系数十分复杂,没有太多优化空间,所以想点别的
我们考虑容斥,钦定那些相邻段的限制没有被满足,如果 \(i, i + 1\) 之间的限制没有满足,意味着 \(i, i + 1\) 的数是连续的,可以接在一起,那么就可以看成一个段
每个段有上升/下降两种选择,但是长度为 \(1\) 的段比较麻烦,只有一种选择,所以一种钦定方案的贡献是:
把这个拍到 dp 里,设状态为 \(f(i, j, 0/1)\) 表示考虑了前 \(i\) 段,现在连成了 \(j\) 个段,最后一段长度等于/大于 \(1\) 的贡献
并且为了简化转移,我们今考虑贡献中 \(2\) 的幂一项的贡献,阶乘跟符号可以最后再乘上
- 若当前段长度为 \(1\),则
- 若当前段长度 \(> 1\),则
因为我们最后只关注 \(f(i, j, 0) + f(i, j, 1)\),不妨用 \(s(i, j) = f(i, j, 0) + f(i, j, 1)\) 代替,将上面的转移式相加,得到
其中 \(k_i\) 在第 \(i\) 段长度等于 \(1\) 时为 \(1\),大于 \(1\) 时为 \(2\)
由于时限 10s,这样就可以过了
正解其实是分治 NTT
将上面的转移式子写成 GF 的形式,设 \(S_i = \sum s(i, j) x^j\),\(F_i = \sum f(i, j, 0) x^j\),则有
利用矩阵乘法转移,当 \(k_i = 1\) 时,转移矩阵为
当 \(k_i = 2\) 时,转移矩阵为
利用分治 NTT 做矩阵乘法,复杂度 \(O(n \log^2 n)\)
CF1495F. Squares
从笛卡尔树出发想会简单一点
CF1534G. A New Beginning
凸优化小白来学 slope trick
一眼看到这题感觉无从下手,因为没有种土豆的顺序,不方便 DP,所以考虑先刻画一下种土豆的顺序,为 DP 创造条件
考虑我们现在固定一条路径,应该在路径上哪些点种哪些土豆(即为每个土豆选择一个种植点)是最优的
路径固定的时候,答案就是所有土豆到路径的距离之和,我们考虑以一个土豆为中心画一个正方形,并不断向外扩展,那么该土豆到路径的距离就是这个正方形第一次接触路径时的边长的一半
这启示我们把路径分成若干个线段,并对每个线段分别考虑在这条线段上种哪些土豆

例如,对于横线,考虑它两边的拐角,过这两个拐角作两条斜率为 \(-1\) 的直线,那么它能覆盖到的土豆就是蓝红线之间的部分(不包括蓝线,但包括红线),竖线则类似
进一步地,我们考虑对于走的每一步,我们可以将其视为长为一的边,那么它能覆盖的范围就是终点所在的那条斜率为 \(-1\) 的直线
所以现在我们就可以 DP 了,相当于从一条斜线走到下一条斜线,把坐标系旋转了 45 度,转移方程类似下式:
slope trick 板子,做完了

浙公网安备 33010602011771号