「2020联考北附1」命运歧途
「2020联考北附1」命运歧途
排列 \(dp\) 问题通常想到容斥,因为很难在 \(dp\) 的同时保证排列元素不多次出现
对于这个问题,我们只需要考虑相邻关系
我们需要计算包含0个非法位置的排列个数,因此容斥容易定义为计算
包含至少 \(i\) 个非法位置的排列个数,容斥系数可以简单设置为 \((-1)^i\)
考虑一个序列的合法与非法情况,容易发现是类似下面的情况
序列会被分成若干段,每一段是形如 \(x,x+k,x+2k,\cdots\) 或者 \(x,x-k,x-2k\)
显然 \(x,y\) 会产生非法关系的必要条件是 \(x\equiv t \pmod k\) ,因此按照 \(x\mod k\) 分组
组内实际上是类似 \(k=1\) 的子问题,不妨设一组包含 \(m\) 个元素
显然不同组之间一定构成不同的段,而对于一组内的元素,我们可以强制某一些位置分段
最终会得到一个若干段的序列,段之间由于不确定相互关系,设最终分成 \(i\) 段,则可以有 \(i!\) 种段之间排列
(分成 \(i\) 段的贡献在我们计算时实际上是至少包含了 \(n-i\) 个非法位置)
现在问题就是落到了 \(dp\) 序列分成 \(i\) 段的方案数,显然对于每一组,是一个分组背包的问题
对于每一个大小为 \(m\) 的组,考虑 \(dp\) 其分段方案
令 \(dp_{i,j}\) 表示当前已经确定的总大小为 \(i\) 且已经分成了 \(j\) 段
转移可以枚举下一段的大小 \(k\) ,由于实际排列中的段是有递增和递减两种情况,因此对于任意 \(k>1\) ,有两种段分配方法
转移式子是
\(dp_{i,j}\rightarrow dp_{i+k,j+1} (k=1)\)
\(2dp_{i,j}\rightarrow dp_{i+k,j+1} (k>1)\)
这个式子容易用前缀和优化,特殊的转移位置只有一个,处理一下即可
于是可以在 \(O(n^2)\) 复杂度内计算任何一个大小的组的分段方案
暴力合并组之间的背包,对于每个查询,复杂度为 \(O(n^2)\)
因此总复杂度受限于查询,为 \(O(qn^2)\)
接下来考虑如何削减查询复杂度
由于 \(q\) 的上限实际是 \(n\) ,下文认为 \(q=n\)
优化1
上面的问题中,我们并没有具体地分析分组的情况
实际上对于 \(n,k\) ,可能的组大小只有两种,即 \(\lfloor \frac{n}{k}\rfloor ,\lceil \frac{n}{k}\rceil\)
不同的 \(\lfloor \frac{n}{k}\rfloor\) 只有 \(O(\sqrt n)\) 种,不妨对于每种 \(\lfloor \frac{n}{k}\rfloor\) 从小到大计算每一个 \(k\) 的答案
简单观察即可发现:
在每个块内,按照 \(k\) 递增,两种组的个数一个递增一个递减
如果在每个 \(\lfloor \frac{n}{k}\rfloor\) 中,为最小的 \(k\) 暴力 \(O(n^2)\) 预处理出答案,然后不断增大
不妨维护两个单调的个数指针
如果能够支持背包回撤一个分组,增加一个分组,那么容易做到每个块内 \(O(n^2)\) 递推答案
增加一个分组不必说,而对于去掉一个分组,不好求出模逆元
但是由于上面的 \(dp_{i,j}\) 满足 \(dp_{i,i}=1\) ,因此考虑从高到低递推每一位
模拟一个长除法即可
合理的常数优化也可以通过此题
1 | const int N=2010; |
\[ \ \]
优化2
从生成函数的角度,我们容易把所求的值归纳为
\(H(x)=F^a(x)G^b(x)\)
我们知道多项式快速幂的复杂度为 \(\exp\) 的 \(n\log n\)
当然那是对于长度为 \(n\) 的情况
而现在是长度之和为 \(n\)
由于EI在无数道题中介绍了这个东西,看到马上想到可以求导
\(H'(x)=aF^{a-1}(x)F'(x)G(x)+bG^{b-1}(x)G'(x)F(x)\)
\(H'=H(a\cdot \frac{F'}{F}+b\cdot \frac{G'}{G})\)
理想的情况是:对于这个式子,两边从低到高解方程确定每一项的值
然而首先遇到的多项式除法操作就难以解决
要除掉的 \(F,G\) 没有常数项,而第一项系数为1或2,因此除法操作只需要计算 \(2\) 的逆元
因此可以考虑对于模数中的 \(2^t\) 提取出来,然后最后暴力exCRT合并
接下来就是分成两部分
计算 \(\mod 2^t\)
由上面知道,背包的每一个位置实际对于最后答案的贡献是 \(i!(-1)^{n-i}\)
因此对于 \(i!\mod 2^t=0\) 的位置都不用考虑,只需要求出背包前 \(O(\log P)\) 位,暴力即可
\[ \ \]
计算 \(\mod P(2\not |P)\)
接下来就是上面的递推过程
然而还有一个问题就是从 \([x^n]H'\) 得到 \([x^{n+1}]H\) ,显然这里需要一个逆元
EI为我们提供一个很好的思路,或许有助于解决任意模数的逆元问题:
因为答案计算的是 \(k![x^k]H\) ,因此可以直接在计算过程中加入
这样求导的系数在阶乘中被省略,变成了单纯的平移
而乘法只需要额外添加一个权值 \(\binom{i+j}{i}\cdot x^i\cdot x^j\rightarrow x^{i+j}\)
接下来具体的方法是:
先将 \(F(x),G(x)\) 平移一位去掉空余的项,处理过后 \(H(x)\) 被平移了 \(a+b\) 的位置,首项为 \(x^0\)
从低到高递推 \(H(x)\) 的每一位,同步维护 \(\frac{H}{F},\frac{H}{G}\)
对于 \(H'(x)\) 的第 \(i\) 项累和得到,然后平移一位得到 \(H(x)\) 的 \(i+1\) 项
最后需要加上平移部分的贡献, \(i!\rightarrow (i+a+b)!\) ,可以用一个组合数解决
1 | const int N=2010; |