多项式基本操作
多项式求逆
给定一个 \(n-1\) 次的多项式 \(A(x)(a_0 \neq 0)\) ,要求一个多项式 \(F(x)\) 满足 \(F(x)A(x) \equiv 1 \pmod x^n\) 。
假设求出了 \(F_0(x)\) 满足 \(F_0(x)A(x) \equiv 1 \pmod{x^{\lceil \frac{n}{2} \rceil}}\) ,那么有
由于 \(A(x) \neq 0\) ,所以
又因为 \(F(x)A(x) \equiv 1 \pmod{x^n}\) ,两边都乘个 \(A(x)\) ,
所以我们可以递归求解,时间复杂度 \(T(n) = T(n/2)+n\log n = n\log n\) 。
void get_inv(int *a, int *f, int n){
if(n == 1)return f[0] = fpow(a[0], mod-2), void();
get_inv(a, f, n+1>>1), init(2*n-1);//init()处理数组长度和蝴蝶变换
fp(i, 0, n-1)inv_ary[i] = a[i];
ntt(f, 0), ntt(inv_ary, 0);
fp(i, 0, lim-1)f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_ary[i]%mod+mod)%mod;
ntt(f, 1);
fp(i, n, lim-1)f[i] = 0;
fp(i, 0, lim-1)inv_ary[i] = 0;
}
多项式求 \(\rm{ln}\) 。
给定一个 \(n-1\) 次的多项式 \(A(x)(a_0 = 1)\) ,要求一个多项式 \(F(x)\) 满足 \(F(x) \equiv \ln A(x) \pmod{x^n}\) 。
两边求个导得到:
所以只需要对 \(A(x)\) 求个导和逆就能求出 \(F'(x)\) 了。最后再对 \(F'(x)\) 求个积分,由于 \(a_0=1\) ,所以常数项为 \(0\) 。时间复杂度为 \(\mathcal{O} (n\log n)\) 。
void get_ln(int *a, int *f, int n){
get_inv(a, ln_ary, n), init(2*n-2);
fp(i, 1, n-1)f[i-1] = 1ll*a[i]*i%mod;
ntt(f, 0), ntt(ln_ary, 0);
fp(i, 0, lim-1)f[i] = 1ll*f[i]*ln_ary[i]%mod;
ntt(f, 1);
fp(i, n-1, lim)f[i] = 0;
fp(i, 0, lim)ln_ary[i] = 0;
fb(i, n-1, 1)f[i] = 1ll*f[i-1]*inv[i]%mod;
f[0] = 0;
}
牛顿迭代
给定一个函数 \(G(x)\) ,要求一个多项式 \(F(x)\) 满足 \(G(F(x)) \equiv 0 \pmod{x^n}\) 。
假设求出了 \(F_0(x)\) 满足 \(G(F_0(x)) \equiv 0 \pmod{x^{\lceil \frac{n}{2}\rceil}}\) ,那么把 \(G(x)\) 在 \(F_0(x)\) 处泰勒展开可以得到:
因为 \(F(x)-F_0(x)\) 最低次是 \(x^{\lceil \frac{n}{2}\rceil}\) ,所以从第三项开始模 \(x^n\) 就为 \(0\) 了。 注意这里的求导是对 \(F_0(x)\) 求导。然后就可以递归计算了。
多项式 \(\rm{exp}\)
给定一个 \(n-1\) 次多项式 \(A(x)(a_0=0)\) ,要求一个多项式 \(F(x)\) 满足 \(F(x) \equiv e^{A(x)} \pmod{x^n}\) 。
两边求个 \(\ln\) : \(\ln F(x) \equiv A(x) \pmod{x^n}\) 。设 \(G(F(x))=\ln F(x)-A(x)\) ,那就相当于求 \(F(x)\) 满足 \(G(F(x)) \equiv 0 \pmod{x^n}\) 。
根据牛顿迭代的式子, \(F(x) \equiv F_0(x)-F_0(x)(\ln F_0(x)-A(x)) \equiv F_0(x)(1-\ln F_0(x)+A(x))\) 。递归求解即可,时间复杂度 \(\mathcal{O} (n\log n)\) 。
void get_exp(int *a, int *f, int n){
if(n == 1)return f[0] = 1, void();
get_exp(a, f, n+1>>1), get_ln(f, exp_ary, n), init(2*n-1);
fp(i, 0, n-1)exp_ary[i] = (a[i]-exp_ary[i]+mod)%mod;
if((++exp_ary[0]) == mod)exp_ary[0] = 0;
ntt(f, 0), ntt(exp_ary, 0);
fp(i, 0, lim-1)f[i] = 1ll*f[i]*exp_ary[i]%mod;
ntt(f, 1);
fp(i, n, lim-1)f[i] = 0;
fp(i, 0, lim-1)exp_ary[i] = 0;
}
分治 \(\rm{FFT/NTT}\)
求出 \(\prod_{i=1}^n(a_{i,0}+a_{i,1}x+a_{i,2}x^2+ \cdots +a_{i,k+1}x^{k+1})\) ,要求复杂度为 \(\mathcal{O} (nk \log^2 nk)\) 。一般情况下 \(k\) 都等于 \(1\) 。
显然不能从左到右乘过去,这样的话复杂度高达 \(\mathcal{O} (n^2)\) 。
考虑分治,每次做完左右两边的乘法,这样左右两边的长度都是 \(\frac{len}{2}\) 的, \(len\) 是当前的分治区间长度。然后把两边的乘起来即可。显然复杂度降到了 \(\mathcal{O} (nk \log^2 nk)\) 。当 \(k=1\) 时就是 \(\mathcal{O} (n \log^2 n)\) 。
贴个 \(k=1\) 时的代码(\(a_0 = 1\))。
int div_ary[20][maxn<<2];
void divntt(int d, int l, int r){
if(l == r)return div_ary[d][0] = 1, div_ary[d][1] = mod-a[l], void();
int md = l+r>>1, len = r-l+2;
divntt(d, l, md), divntt(d+1, md+1, r), init(len), ntt(div_ary[d], 0), ntt(div_ary[d+1], 0);
fp(i, 0, lim-1)div_ary[d][i] = 1ll*div_ary[d][i]*div_ary[d+1][i]%mod;
ntt(div_ary[d], 1);
fp(i, len, lim-1)div_ary[d][i] = 0;
fp(i, 0, lim-1)div_ary[d+1][i] = 0;
}
常见的数
第一类斯特林数
定义 :\(\begin{bmatrix} n\\m \end{bmatrix}\) 表示把 \(n\) 个有标号元素划分成 \(m\) 个圆排列的方案数。
递推公式 :
考虑加入第 \(n\) 个元素,它要么新成一个环,要么排在前 \(n-1\) 个元素中某一个的后面。
组合意义 : \(n! = \sum_{i=0}^n\begin{bmatrix} n\\i \end{bmatrix}\)
可以理解为一个排列对应为若干个置换,每个置换又对应一个圆排列。
生成函数 :\(\sum_{i=0}^{\infty} \begin{bmatrix} n\\i \end{bmatrix}x^i=x^{\overline{n}}\)
考虑归纳法,首先有 \(\begin{bmatrix} 0\\0 \end{bmatrix}x^0=x^{\overline{0}},\begin{bmatrix} 1\\0 \end{bmatrix}x^0+\begin{bmatrix} 1\\1 \end{bmatrix}x^1=x^{\overline{1}}\) 。
所以 \(\sum_{i=0}^{\infty} \begin{bmatrix} n+1\\i \end{bmatrix}x^i=x^{\overline{n+1}}\)
考虑求这个生成函数。直接的方法可以用分治 \(\rm{NTT}\) 求,复杂度 \(\mathcal{O} (n\log^2 n)\)。还有种倍增的方法。
设 \(F_n(x) = x^{\overline{n}}\) ,考虑怎么通过它求出 \(F_{2n}(x)\) 。显然有 \(F_{2n}=F_n(x)F_n(x+n)\) 。
很容易看出来后面的式子可以卷积。设 \(a_i=i!\begin{bmatrix} n\\i \end{bmatrix},b_i=\frac{n^i}{i!}\),将 \(a\) 的下标反转一下,卷积的第 \(n-j\) 次项系数除以 \(j!\) 就是 \(F_n(x+n)\) 的第 \(j\) 次项系数。现在就可以递归求解了,当 \(n\) 为奇数时就将 \(n\) 减一,求出多项式后乘一个 \(x+n-1\) 即可。复杂度 \(\mathcal{O} (n\log n)\) 。
第二类斯特林数
定义 : \(\begin{Bmatrix} n\\m \end{Bmatrix}\) 表示将 \(n\) 个有标号元素划分为 \(m\) 个无标号非空集的方案数。
递推公式 :
考虑加入第 \(n\) 个元素,它要么新成一个集合,要么被划进已有的元素。
组合意义 : \(m^n = \sum_{i=0}^m{m\choose i}\begin{Bmatrix} n\\i \end{Bmatrix}i!\)
因为左边是 \(n\) 个有标号元素划分为任意个有标号集合的方案数,那么枚举有几个非空集,很自然能得到右边的式子。
通项公式 :
有两种方法理解这个式子:
由于不能选空集,那就容斥掉空集的情况。所以枚举选了 \(i\) 个空集,然后将所有 \(n\) 个元素放入 \(m-i\) 个集合中(可以为空集)。容易看出来容斥系数为 \((-1)^i{m\choose i}\) 。
或者考虑二项式反演:
有了这个通项之后就可以 \(\mathcal{O} (n\log n)\) 求解一行第二类斯特林数了,因为把组合数打开可以发现是卷积的形式。
幂转换公式
首先有上面的 \(x^{\overline{n}}=\sum_{i=0}^{\infty} \begin{bmatrix} n\\i \end{bmatrix}x^i\) 和 \(x^n = \sum_{i=0}^{\infty}\begin{Bmatrix} n\\i \end{Bmatrix}x^{\underline i}\)
由 \(x^{\overline n} = (-1)^n x^{\underline n}\),可以得到另外两个幂转换公式:
贝尔数
定义 : \(B_n\) 表示将 \(n\) 个有标号元素划分为任意个无标号非空集合的方案数。
递推公式 :
考虑加入第 \(n+1\) 个元素,枚举它与多少个元素划进同一个集合,剩下的元素任意划分。
组合意义 :
这样的话后面的 \(\sum\) 就只与上界有关了,先给后面的 \(\sum\) 求个前缀和,再线筛 \(i^n\) ,就可以 \(\mathcal{O}(n)\) 求解一个贝尔数了。
生成函数 : \(\sum_{n=0}^{\infty}B_nx^n = e^{e^x-1}\) 。
写出 \(n\) 个数划分成一个集合的方案数 \(f_n\) ,显然 \(f_n=1\) 。考虑划分成任意个集合的方案数就是 \(e^{F(x)}\) ,其中 \(F(x) = \sum_{n=1}^{\infty}\frac{f_n}{n!}x^n = e^x-1\) 。所以写一个多项式 \(\rm{exp}\) 就可以 \(\mathcal{O} (n\log n)\) 求解前 \(n\) 个贝尔数了。
通项公式 :
所以通项为 \(\frac{1}{e}\sum_{n=0}^{\infty}\frac{n^i}{n!}\) 。
\(\rm Touchard\) 同余
\(B_{n+p}\equiv B_n+B_{n+1}\pmod{p}\)。
引理 \(1\) :\(B_{n+m}=\sum_{i=0}^n\sum_{j=0}^m j^{n-i}\begin{Bmatrix}m\\j\end{Bmatrix}{n\choose i}B_i\)
把n+m个元素分成n个元素和m个元素。首先枚举m个元素划分成了j个集合,就有了\(\begin{Bmatrix}m\\j\end{Bmatrix}\);再枚举n个元素中选i个出来任意划分,就有了\({n\choose i}B_i\);再把剩下的n-i个元素随意放进m个元素的那j个集合里,就有了\(j^{n-i}\) 。根据乘法原理乘起来,就是\(B_{n+m}\)。正确性是因为每种划分方案都可以像这样描述。
引理 \(2\) : \(\begin{Bmatrix}n\\m\end{Bmatrix}\equiv0\pmod n(1<m<n),n\)为质数 。
展开左边的式子:\(\begin{Bmatrix}n\\m\end{Bmatrix}=\frac{1}{m!}\sum_{i=0}^m(-1)^i{m\choose i}(m-i)^n\),可以发现组合数的m!可以跟前面约了:
根据费马小定理,可以把m-i的指数消一下:\(\equiv\sum_{i=0}^m\frac{(-1)^i}{i!}\frac{m-i}{(m-i)!}\pmod n\)
当\(i=m\)的时候后面就=0,所以\(i\)最多等于\(m-1\),那么根据二项式定理就可得:
所以当\(1<m<n\)的时候\(\begin{Bmatrix}n\\m\end{Bmatrix}\)就同余0。
证明 \(\rm Touchard\) 同余:
最后我们写出引理1的式子:
根据引理\(2\),当\(1<j<p\)时,后边的式子值为\(0\)。又根据第二类斯特林数的定义,当\(j=0\)时,后边也为\(0\),当\(j=1\)或者\(p\)时,\(\begin{Bmatrix}p\\j\end{Bmatrix}=1\),。所以这个式子可以进一步写成:
根据最开始的\(n^2\)的递推式,加号左边的就是\(B_{n+1}\);由于是模意义下的,所以当\(i\neq n\)时,\(p^{n-i}{n\choose i}B_i\equiv 0\pmod{p}\)。。当i=n时,这个式子等于\(B_n\)。
所以\(B_{n+p}\equiv B_n+B_{n+1}\pmod{p}\)
划分数
定义 : \(p_{n,m}\) 表示将 \(n\) 划分成 \(m\) 个无标号 自然数 和的方案数。
递推公式 : \(p_{n,m} = p_{n-m,m}+p_{n,m-1}\) 。
要么给已有的 \(m\) 个数都加 \(1\) ,要么加入一个 \(0\) 。
生成函数 : \(P_n(x) = \sum_{i=0}^{\infty}p_{i,n}x^i\) 。
根据递推公式,
又有 \(P_1(x) = \sum_{i=0}^{\infty}x^i=\frac{1}{1-x}\) ,不难发现
直接的方法就是分治 \(\rm NTT\) ,复杂度 \(\mathcal{O} (n\log^2 n)\) 。
观察到 \(x^i\) 的系数都是 \(-1\) ,考虑继续优化:
设 \(F_i(x) = \ln(1-x^i)\) ,
所以暴力确定 \(\ln P_n(x)\) 的系数是 \(\mathcal{O} (n\ln n)\) 的,再做个 \(\rm exp\) 就可以求得 \(P_n(x)\) 。
一些奇技淫巧
\(\rm EularTransform\)
给定长度为 \(n\) 的数列 \(a\) ,要求 \(F(x) = \sum_{i=1}^n\frac{1}{1-a_ix}\) 。
当然可以通分+分治 \(\rm NTT\) ,但那样常数有点大。
\(\rm EularTransform\) 的思想是通过 \(1-x(\ln (1-a_ix))' = 1+\frac{a_i}{1-a_ix}=\frac{1}{1-a_ix}\) ,得到
\(\prod\) 就用分治 \(\rm NTT\) 算,复杂度 \(\mathcal{O} (n\log^2 n)\) 。
不知道叫啥
给定长度为 \(n\) 的数列 \(a\) 和多项式 \(F(x)\) ,要求 \(\sum_{i=1}^nF(a_ix)\) 。
暴力做显然是 \(\mathcal{O} (n^2)\) 的。
注意到最后的多项式的第 \(m\) 次项系数肯定是 \(\sum_{i=1}^na_i^m\) 。考虑写出这玩意儿的 \(\rm OGF\) :
剩下的就是上面的 \(\rm EularTransform\) 了。
自然数幂和
对于所有 \(n\in [0,N]\) 求出 \(\sum_{i=0}^mi^n\) ,也就是刚才那个东西的特殊形式。
这次我们写出 \(\rm EGF\) :
给分母展开求个逆再乘上分母即可。注意到分母的常数项为 \(0\) 不能直接求逆,但是分子的常数项也为 \(0\) ,同时除以 \(x\) 即可。复杂度 \(\mathcal{O} (n\log n)\) 。
任意模数 \(\rm NTT\)
求 \(A(x)B(x)\) 的系数对\(10^9+7\) 取模的结果,最高次项是 \(10^5\) 级别,系数是 \(10^9\) 级别。
分析可得最后系数的值域应是 \(10^{23}\) 以内。那么对 \(998244353,1004535809,469762049\) 三个模数分别做 \(\rm NTT\) ,就能用 \(\rm CRT\) 解出真实值(因为三个模数乘起来大于 \(10^23\) ),再对 \(10^9+7\) 取模即可。选这三个因为原根都是 \(3\) 。
咕咕咕