「ABC272Ex」Flipping Coins 2
题目
点这里看题目。
你有一个长度为 \(N\) 的序列 \(\{A_k\}_{k=1}^N\),序列每个元素都是 \([0,N-1]\) 范围内的整数。
你还有另外一个长度为 \(N\) 的序列 \(\{B_k\}_{k=0}^{N-1}\)。初始时,\(B\) 中所有元素都是 \(0\)。
假设有一个 \(1\sim N\) 的排列 \(p\),那么进行 \(N\) 次操作,对于第 \(k\) 次操作(\(1\le k\le N\)),你需要:
- 对于 \(0\le j\le A_{p_k}\),将 \(B_{(k-1+j)\bmod N}\) 加一。
设这个排列的权值为操作完成后 \(B\) 中偶数元素的个数。求等概率从 \(S_N\) 选一个排列进行操作时,排列权值的期望。
答案对 \(998244353\) 取模。
所有数据满足 \(1\le N\le 2\times 10^5,0\le A_k<N\)。
分析
Ex 这么有实力的吗?
很显然我们可以直接统计排列权值之和,然后变成统计 \(B\) 中每个元素是偶数的次数。很显然根据对称性,最终 \(B\) 中每个元素是偶数的次数是一样的,所以我们研究一个 \(B\) 即可。为了避免讨论环,我们选择 \(B_{N-1}\)。
那么我们已经可以写出一个 DP 了:记 \(c_v=\sum_{k=1}^N[A_k=v]\),则设 \(f_{i,j}\) 表示最终排列中后 \(i\) 个元素中,还剩 \(j\) 个空位的方案数。转移很 common,不说了。
DP 结束之后,需要做一个二项式反演,然后求一个和,就可以得到答案。
然而上面的方法很恼火,直接优化难以降到 \(O(n^2)\) 以下。主要有两点让人不适:
-
转移形式很复杂。由于同样元素,我们不得不在转移里面引入和式,而且在 \(c_v\) 的背景之下难以去除。
-
转移中,求和指标始终配的是一个负系数。这导致我们在向 GF 等构造靠拢时,不得不引入 \(x^{-1}\),不自然。
-
阻力最大的一点是,我们需要及时去除 \(j<0\) 的项。这一点基本上直接阻断了任何形式的卷积优化。
比较难办的 1、3 两点,指向的都是转移形式复杂,暗示应当抛弃“分组转移”的策略,所以我们尝试把转移均摊到每一次操作上。换言之,把 \(A\) 排序之后,我们可以设 \(L_i=\max_{A_j\ge N-i+1}j\),则第 \(i\) 次操作可以翻转 \(N-1\) 当且仅当 \(p_i\ge L_i\),这就变成了关于 \(p\) 元素本身的限制。(这里的 \(p\) 指向的是题面中的“排列”)
Remark.
仍然是思考了很久的问题:如何避免“前期得到本质相同但形式不好的结果,导致后续的优化无法进行”这样的问题?
至少从这道题中,可以得到一点启发:在进行后续的优化之前,先尝试将当前的形式化得更简洁一些。尤其是后续优化遇到困难的时候,更有必要重新铺一铺前面的路。
代数工具不一定是万能的,尤其是工具本身就不齐全的前提下。所以不要把所有的障碍全部堆积起来,试图在数学推导过程中解决!
显然 \(B_i\) 是单调不增的(这和原来那个 DP 依赖的单调性是相似的),我们仍然可以轻易地写出一个 DP:\(f_{i,j}\) 表示最终排列的前 \(i\) 项中,可以贡献到 \(B_{N-1}\) 的项有 \(j\) 个的方案数。
由于转移比较重要,我们还是写一下:
考虑优化这个运算过程。结合这道题的 Ex 身份以及 \(998244353\) 这个模数,我们尝试向生成函数这个方向靠一靠。
很显然,我们应当将 \(j\) 作为指数。注意到 \(j\) 也作为系数出现,我们可能需要用到导数。但是你在什么时候见到过像 \(x^{j-1}\rightarrow jx^j\) 这种“不仅指数进入系数,次数还要上升”的变换呢?很显然,我们需要提前处理一下——翻转下标。
那么,我们令 \(g_{i,j}=f_{i,i-j}\),于是得到新的转移:
Remark.
如果这里不翻转会怎么样?我们可能不得不面对 \(\vartheta x=x(\vartheta+1)\) 这样复杂的算子。
有时候这样的转换可能看起来比较朴素,但是自己操作起来就不一定想得到。
有时候可能觉得这样的转换“没有意义”,因为“搞不好之后哪里就会出现对称的障碍,导致‘翻转’失效”。——但是,既然当前不会失效,就先用它解决这个问题呗。
设 \(G_i(x)=\sum_{j\ge 0}g_{i,j}x^j,C_i=N-L_i-i+1\),我们有:
到这里,这些处理都还算比较可以理解的。但是下面这一步,确实就有一点出人意料了——我们需要在左右两侧同时配凑一个因式 \(e^x\),看一看有什么变化:
Remark.
从数列推导的角度来说,这种方法其实是很常见的嘛。
我们的目的,其实就是求出 \(H_N(x)\) 的通项式,而已知的是递推式。这里的操作,可以和 “求和因子”方法挂钩:配一个因子,目的在简化求和。
这里不同的地方在于,递推式中有导数出现,所以配的因子也和普通数列有所不同。如果从构造过程来看的话,可能还是从 \(G_{i-1}(x)+G_{i-1}'(x)\) 的结构出发比较好——这样我们可以想到用 \(e^x\) 把它收拢。
奇妙的结果。我们发现 \(G_i(x)e^x\) 可以作为整体得到一个新的递推式。设 \(H_i(x)=G_i(x)e^x\),则 \(H_i(x)=xH'_{i-1}(x)+C_iH_{i-1}(x)\)。提取 \([x^j]\):
很简洁!这是令人激动的结果!因为我们已知 \(H_0(x)=e^x\),所以我们马上就可以得到:
这是什么?后面的乘积式是关于 \(j\) 的多项式函数,所以我们只需要进行一次多点求值即可得到 \(h_N\)!知道了 \(H_N(x)\),后续的 \(G_N\) 以及二项式反演,也就都不成问题了。
所以,我们可以 \(O(N\log^2N)\) 地解决这个问题。
代码
代码就是套板子,而且我不想写多点求值,所以没有写代码。