多项式
拉格朗日插值
- 已知最高次数不超过 n − 1 n - 1 n−1 的多项式在平面上的 n n n 个点 ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) (x_1,y_1),(x_2,y_2),\dots,(x_n,y_n) (x1,y1),(x2,y2),…,(xn,yn),满足 ∀ 1 ≤ i < j ≤ n , x i ≠ x j \forall 1 \le i < j \le n, x_i\not=x_j ∀1≤i<j≤n,xi=xj,则可还原出多项式:
f ( x ) = ∑ i = 1 n y i ∏ j ≠ i x − x j x i − x j f(x) = \sum\limits_{i = 1}^{n}y_i\prod\limits_{j \not=i} \frac{x - x_j}{x_i - x_j} f(x)=i=1∑nyij=i∏xi−xjx−xj
- 若 x i = i x_i = i xi=i,且 x > n x > n x>n,则该式可简化为:
f ( x ) = ( ∏ i = 1 n ( x − i ) ) ( ∑ i = 1 n ( − 1 ) n − i y i ( x − i ) ( i − 1 ) ! ( n − i ) ! ) f(x) = \left(\prod\limits_{i = 1}^{n}(x - i)\right)\left(\sum\limits_{i = 1}^{n}\frac{(-1)^{n-i}y_i}{(x-i)(i-1)!(n - i)!} \right) f(x)=(i=1∏n(x−i))(i=1∑n(x−i)(i−1)!(n−i)!(−1)n−iyi)
- 预处理相关逆元和前缀积,即可 O ( n ) \mathcal O(n) O(n) 计算。
- 若已知二元多项式在空间内的 n m nm nm 个点 ( x 11 , y 11 , z 11 ) , … , ( x n m , y n m , z n m ) (x_{11},y_{11},z_{11}),\dots,(x_{nm},y_{nm},z_{nm}) (x11,y11,z11),…,(xnm,ynm,znm),满足任意两点横纵坐标至少一个不相等,且该多项式中 x x x 的最高次数不超过 n − 1 n - 1 n−1, y y y 的最高次数不超过 m − 1 m - 1 m−1,则可还原出多项式:
f ( x , y ) = ∑ i = 1 n ∑ j = 1 m z i j ∏ k ≠ i x − x k j x i j − x k j ∏ l ≠ j x − x i l x i j − x i l f(x,y) = \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{m}z_{ij}\prod\limits_{k\not=i}\frac{x-x_{kj}}{x_{ij}-x_{kj}}\prod\limits_{l\not = j}\frac{x-x_{il}}{x_{ij}-x_{il}} f(x,y)=i=1∑nj=1∑mzijk=i∏xij−xkjx−xkjl=j∏xij−xilx−xil
FFT
- 即快速傅里叶变换。
- 设 n n n 次单位根 ω n k = cos 2 π k n + i sin 2 π k n \omega_{n}^k = \cos\frac{2\pi k}{n} + i\sin\frac{2\pi k}{n} ωnk=cosn2πk+isinn2πk。
- 将多项式 A , B A,B A,B 的项数补到 2 的整数次幂,设项数为 n n n,采用分治法快速求出 A , B A,B A,B 代入 ω n 0 , ω n 1 , … , ω n n − 1 \omega_{n}^0, \omega^1_n, \dots, \omega^{n - 1}_{n} ωn0,ωn1,…,ωnn−1 的点值,将点值相乘再通过类似的过程还原回多项式,即可快速求出多项式 A × B A \times B A×B,时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
DFT
- 即离散傅里叶变换。
- 对于多项式
A ( x ) = ∑ k = 0 n − 1 a k x k = ∑ k = 0 n 2 − 1 ( a 2 k x 2 k + a 2 k + 1 x 2 k + 1 ) = ∑ k = 0 n 2 − 1 a 2 k ( x 2 ) k + x ∑ k = 0 n 2 − 1 a 2 k + 1 ( x 2 ) k A(x) = \sum\limits_{k = 0}^{n - 1}a_k x^k = \sum \limits_{k = 0}^{\frac{n}{2} - 1}(a_{2k}x^{2k} + a_{2k + 1}x^{2k + 1}) = \sum \limits_{k = 0}^{\frac{n}{2} - 1}a_{2k}(x^{2})^k + x\sum\limits_{k = 0}^{\frac{n}{2} - 1}a_{2k+1}(x^2)^k A(x)=k=0∑n−1akxk=k=0∑2n−1(a2kx2k+a2k+1x2k+1)=k=0∑2n−1a2k(x2)k+xk=0∑2n−1a2k+1(x2)k
- 设 A 1 ( x ) = ∑ k = 0 n 2 − 1 a 2 k x k , A 2 ( x ) = ∑ i = 0 n 2 − 1 a 2 k + 1 x k A_1(x) = \sum \limits_{k = 0}^{\frac{n}{2} - 1}a_{2k}x^k, A_2(x) = \sum\limits_{i = 0}^{\frac{n}{2}-1}a_{2k + 1}x^k A1(x)=k=0∑2n−1a2kxk,A2(x)=i=0∑2n−1a2k+1xk,若已求得 A 1 ( ω n 2 k ) , A 2 ( ω n 2 k ) , 0 ≤ k ≤ n 2 − 1 A_1(\omega_\frac{n}{2}^k), A_2(\omega^{k}_{\frac{n}{2}}), 0 \le k \le \frac{n}{2} - 1 A1(ω2nk),A2(ω2nk),0≤k≤2n−1,则
A ( ω n k ) = A 1 ( ω n 2 k ) + w n k A 2 ( w n 2 k ) = A 1 ( ω n 2 k ) + w n k A 2 ( ω n 2 k ) A ( ω n k + n 2 ) = A 1 ( ω n 2 k ) − w n k + n 2 A 2 ( w n 2 k ) = A 1 ( ω n 2 k ) − w n k A 2 ( ω n 2 k ) A(\omega_n^k) = A_1(\omega^{2k}_{n}) + w_{n}^{k}A_2(w_{n}^{2k}) = A_1(\omega^{k}_{\frac{n}{2}}) + w_{n}^{k}A_2(\omega^{k}_{\frac{n}{2}}) \\ A(\omega_n^{k + \frac{n}{2}}) = A_1(\omega^{2k}_{n}) - w_{n}^{k+\frac{n}{2}}A_2(w_{n}^{2k}) = A_1(\omega^{k}_{\frac{n}{2}}) -w_{n}^{k}A_2(\omega^{k}_{\frac{n}{2}}) \\ A(ωnk)=A1(ωn2k)+wnkA2(wn2k)=A1(ω2nk)+wnkA2(ω2nk)A(ωnk+2n)=A1(ωn2k)−wnk+2nA2(wn2k)=A1(ω2nk)−wnkA2(ω2nk)
- 不断分治下去,此时我们便求得了 A ( ω n k ) , 0 ≤ k ≤ n − 1 A(\omega^{k}_n), 0 \le k \le n - 1 A(ωnk),0≤k≤n−1。
IDFT
- 即逆离散傅里叶变换,由之前的叙述我们有:
[ ( ω n 0 ) 0 ( ω n 0 ) 1 ⋯ ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 ⋯ ( ω n 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n n − 1 ) 0 ( ω n n − 1 ) 1 ⋯ ( ω n n − 1 ) n − 1 ] [ a 0 a 1 ⋮ a n − 1 ] = [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[ \begin{matrix} (\omega_n^0)^0 & (\omega_n^0)^1 & \cdots & (\omega^0_n)^{n - 1}\\ (\omega_n^1)^0 & (\omega_n^1)^1 & \cdots & (\omega^1_n)^{n - 1}\\ \vdots & \vdots & \ddots & \vdots\\ (\omega_n^{n - 1})^0 & (\omega_n^{n-1})^1 & \cdots & (\omega^{n - 1}_n)^{n - 1}\\ \end{matrix} \right] \left[ \begin{matrix} a_0 \\ a_1 \\ \vdots \\ a_{n - 1} \\ \end{matrix} \right] = \left[ \begin{matrix} A(\omega_n^0)\\ A(\omega_n^1) \\ \vdots \\ A(\omega_n^{n - 1}) \\ \end{matrix} \right] (ωn0)0(ωn1)0⋮(ωnn−1)0(ωn0)1(ωn1)1⋮(ωnn−1)1⋯⋯⋱⋯(ωn0)n−1(ωn1)n−1⋮(ωnn−1)n−1 a0a1⋮an−1 = A(ωn0)A(ωn1)⋮A(ωnn−1)
- 对矩阵求逆,可得:
[ a 0 a 1 ⋮ a n − 1 ] = 1 n [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 ⋯ ( ω n − 0 ) n − 1 ( ω n − 1 ) ) 0 ( ω n − 1 ) 1 ⋯ ( ω n − 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n − ( n − 1 ) ) 0 ( ω n − ( n − 1 ) ) 1 ⋯ ( ω n − ( n − 1 ) ) n − 1 ] [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[ \begin{matrix} a_0 \\ a_1 \\ \vdots \\ a_{n - 1} \\ \end{matrix} \right] = \frac{1}{n} \left[ \begin{matrix} (\omega_n^{-0})^0 & (\omega_n^{-0})^1 & \cdots & (\omega^{-0}_n)^{n - 1}\\ (\omega_n^{-1}))^0 & (\omega_n^{-1})^1 & \cdots & (\omega^{-1}_n)^{n - 1}\\ \vdots & \vdots & \ddots & \vdots\\ (\omega_n^{-(n - 1)})^0 & (\omega_n^{-(n-1)})^1 & \cdots & (\omega^{-(n - 1)}_n)^{n - 1}\\ \end{matrix} \right] \left[ \begin{matrix} A(\omega_n^0)\\ A(\omega_n^1) \\ \vdots \\ A(\omega_n^{n - 1}) \\ \end{matrix} \right] a0a1⋮an−1 =n1 (ωn−0)0(ωn−1))0⋮(ωn−(n−1))0(ωn−0)1(ωn−1)1⋮(ωn−(n−1))1⋯⋯⋱⋯(ωn−0)n−1(ωn−1)n−1⋮(ωn−(n−1))n−1 A(ωn0)A(ωn1)⋮A(ωnn−1)
- 不难发现,将 ω n k \omega_n^k ωnk 替换成 ω n − k \omega_n^{-k} ωn−k 再做一遍 DFT,最后将结果除以 n n n 即可实现 IDFT。
位逆序变换
- 为了减小递归实现带来的常数,我们考虑直接得到递归最底层的排列顺序,再逐层向上合并。
- 不难证明,设 n = 2 m n = 2^m n=2m,则系数 a i a_i ai 递归到最底层时的下标恰好为 i i i 在 m m m 位二进制表示下的对称翻转,我们称这个变换为位逆序变换(蝴蝶变换)。
- 设 r e v [ i ] rev[i] rev[i] 表示系数 a i a_i ai 递归到最底层时的下标,则显然有递推式
int m = 0, _n = na + nb;
for (n = 1; n <= _n; n <<= 1)
++m;
for (int i = 1; i < n; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (m - 1));
typedef long double ld;
typedef complex<ld> com;
const ld pi = acos(-1.0);
inline void DFT(vector<com> &a, int opt)
{
int n = a.size();
for (int i = 0; i < n; ++i)
if (i < rev[i])
std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1)
{
com w(cos(pi / k), opt * sin(pi / k));;
for (int i = 0; i < n; i += k << 1)
{
com res(1.0, 0.0);
for (int j = 0; j < k; ++j)
{
com u = a[i + j],
v = res * a[i + j + k];
a[i + j] = u + v;
a[i + j + k] = u - v;
res = res * w;
}
}
}
if (opt == -1)
{
for (int i = 0; i < n; ++i)
a[i] /= n;
}
}
NTT
- 即快速数论变换。
- 在模质数 P P P 意义下,原根 g g g 具有单位根的性质:
( g k ) P − 1 ≡ 1 ( m o d P ) , g P − 1 2 ≡ − 1 ( m o d P ) (g^k)^{P - 1} \equiv 1(\mod P), g^{\frac{P - 1}{2}}\equiv -1(\mod P) (gk)P−1≡1(modP),g2P−1≡−1(modP)
- 若 P = 2 x a + 1 P = 2^xa +1 P=2xa+1,设 n = 2 m n = 2^m n=2m,用 g 2 x − m a g^{2^{x - m}a} g2x−ma 替换 ω n \omega_n ωn 即可实现 NTT。
- 常见的 P P P 有:
P = 1004535809 = 479 × 2 21 + 1 , g = 3 P = 998244353 = 7 × 17 × 2 23 + 1 , g = 3 P = 1004535809 = 479 \times 2 ^ {21} + 1, g = 3 \\ P = 998244353 = 7 \times 17 \times 2^{23} + 1, g = 3 P=1004535809=479×221+1,g=3P=998244353=7×17×223+1,g=3
- 实现时可以预处理 g 2 x − m a g^{2^{x - m}a} g2x−ma 的幂次,减小常数,代码见多项式基本操作。
多项式基本操作
- 相关数组应开到题目给定长度的四倍,线性逆元应预处理到题目给定长度的两倍。
快速卷积的变式
- 欲求 h k = ∑ i = 0 n − k f i g i + k h_k = \sum \limits_{i = 0}^{n - k}f_ig_{i+k} hk=i=0∑n−kfigi+k,令 g n − i − k ′ = g i + k , h n − k ′ = h k g'_{n - i - k} = g_{i + k}, h'_{n - k} = h_k gn−i−k′=gi+k,hn−k′=hk,则原式可变为:
h n − k ′ = ∑ i = 0 n − k f i g n − k − i ′ h'_{n - k} = \sum \limits_{i = 0}^{n - k}f_ig'_{n - k -i} hn−k′=i=0∑n−kfign−k−i′
- 做快速卷积即可。
分治NTT
- 以计算 f i = ∑ j = 1 i f i − j g j ( 1 ≤ i < n ) f_i = \sum\limits_{j = 1}^{i}f_{i -j}g_j(1 \le i < n) fi=j=1∑ifi−jgj(1≤i<n) 为例, g 1 , g 2 , … , g n − 1 g_1, g_2, \dots, g_{n - 1} g1,g2,…,gn−1 已知, f 0 = 1 f_0 = 1 f0=1。
- 对于当前区间 [ l , r ] [l,r] [l,r],取中点 m i d mid mid。
- 先递归区间 [ l , m i d ] [l, mid] [l,mid]。
- 做 g 1 , … , g r − l g_1, \dots, g_{r - l} g1,…,gr−l 和 f l , f l + 1 , … , f m i d f_{l}, f_{l + 1},\dots,f_{mid} fl,fl+1,…,fmid 的卷积,求出两者对 f m i d + 1 , f m i d + 2 , … , f r f_{mid + 1}, f_{mid + 2}, \dots, f_r fmid+1,fmid+2,…,fr 的贡献。
- 再递归区间 [ m i d + 1 , r ] [mid + 1, r] [mid+1,r]。
- 时间复杂度 O ( n log 2 n ) \mathcal O(n\log^2n) O(nlog2n)。
牛顿迭代
- 当前有一个函数 g g g,要求解出一个多项式 f f f 的前 n n n 项,使得 g ( f ) = 0 g(f) = 0 g(f)=0。
- 已知 f f f 的前 n n n 项 f 0 f_0 f0,现在想求出 f f f 的前 2 n 2n 2n 项,由泰勒展开:
0 ≡ g ( f 0 ) + g ′ ( f 0 ) ( f − f 0 ) ( m o d x 2 n ) f ≡ f 0 − g ( f 0 ) g ′ ( f 0 ) ( m o d x 2 n ) 0 \equiv g(f_0) + g'(f_0)(f - f_0) (\mod x^{2n}) \\ f \equiv f_0 - \frac{g(f_0)}{g'(f_0)}(\mod x^{2n}) \\ 0≡g(f0)+g′(f0)(f−f0)(modx2n)f≡f0−g′(f0)g(f0)(modx2n)
多项式求逆
- 考虑和牛顿迭代类似的倍增法。
- 设当前求出 B 0 ( x ) B_0(x) B0(x) 使得 A ( x ) B 0 ( x ) ≡ 1 ( m o d x n ) A(x)B_0(x) \equiv 1 (\mod x^n) A(x)B0(x)≡1(modxn),则
[ A ( x ) B 0 ( x ) − 1 ] 2 ≡ 0 ( m o d x 2 n ) A ( x ) [ 2 B 0 ( x ) − A ( x ) B 0 2 ( x ) ] ≡ 1 ( m o d x 2 n ) [A(x)B_0(x) - 1]^2 \equiv 0(\mod x^{2n}) \\ A(x)[2B_0(x) - A(x)B_0^2(x)] \equiv 1 (\mod x^{2n})\\ [A(x)B0(x)−1]2≡0(modx2n)A(x)[2B0(x)−A(x)B02(x)]≡1(modx2n)
- 不断迭代下去即可,时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn)。
多项式取对数
- 对 ln ( A ( x ) ) \ln(A(x)) ln(A(x)) 求导后再积分回来,则 B ( x ) = ∫ A ′ ( x ) A ( x ) d x B(x) = \int \frac{A'(x)}{A(x)} \text{d}x B(x)=∫A(x)A′(x)dx。
- 需保证 A ( x ) A(x) A(x) 的常数项为 1。
多项式求指数
- 套用牛顿迭代,令 g ( x ) = ln ( x ) − A g(x) = \ln(x) - A g(x)=ln(x)−A,最终要使 g ( B ) = 0 g(B) = 0 g(B)=0,代入上式,有
B ( x ) ≡ B 0 ( x ) [ 1 − ln B 0 ( x ) + A ( x ) ] ( m o d x 2 n ) B(x) \equiv B_0(x) [1 - \ln B_0(x) + A(x)] (\mod x^{2n}) B(x)≡B0(x)[1−lnB0(x)+A(x)](modx2n)
- 需保证 A ( x ) A(x) A(x) 的常数项为 0。
多项式开根
- 套用牛顿迭代,令 g ( x ) = x 2 − A g(x) = x^2 - A g(x)=x2−A,最终要使 g ( B ) = 0 g(B) = 0 g(B)=0,代入上式,有
B ( x ) ≡ B 0 ( x ) 2 + A ( x ) 2 B 0 ( x ) ( m o d x 2 n ) B(x) \equiv \frac{B_0(x)^2 + A(x)}{2B_0(x)}(\mod x^{2n}) B(x)≡2B0(x)B0(x)2+A(x)(modx2n)
- 若 A ( x ) A(x) A(x) 的常数项不为 1,初始时求 B B B 的常数项需求二次剩余。
多项式快速幂
- 朴素的算法即直接倍增,时间复杂度 O ( n log 2 n ) \mathcal O(n\log^2 n) O(nlog2n)。
- 如果没有截断的情况,直接对点值求 k k k 次方也是正确的。
- 若 A ( x ) A(x) A(x) 的常数项为 1,即求 e k ln A ( x ) e^{k\ln A(x)} eklnA(x), k k k 对模数 P P P 取模。
- 若 A ( x ) A(x) A(x) 的常数项不为 1,找到第一个系数非 0 的项,设为 a 0 = [ x t ] A ( x ) a_0 = [x^t]A(x) a0=[xt]A(x),则所求变为 a 0 k x t k ( A ( x ) a 0 x t ) k a_0^kx^{tk}\left(\frac{A(x)}{a_0x^t}\right)^k a0kxtk(a0xtA(x))k,乘以 a k a^k ak 时 k k k 对 φ ( P ) = P − 1 \varphi(P) = P - 1 φ(P)=P−1 取模。
const int mod = 998244353;
const int inv2 = 499122177;
const int inv3 = 332748118;
int rev[N4], tw[N4], inv[N4];
inline void operator += (vector<int> &a, vector<int> b)
{
int n = b.size();
a.resize(Max((int)a.size(), n));
for (int i = 0; i < n; ++i)
add(a[i], b[i]);
}
inline void operator -= (vector<int> &a, vector<int> b)
{
int n = b.size();
a.resize(Max((int)a.size(), n));
for (int i = 0; i < n; ++i)
dec(a[i], b[i]);
}
inline void operator *= (vector<int> &a, int k)
{
if (k == -1)
{
int n = a.size();
for (int i = 0; i < n; ++i)
if (a[i])
a[i] = mod - a[i];
}
else
{
int n = a.size();
for (int i = 0; i < n; ++i)
a[i] = 1ll * k * a[i] % mod;
}
}
inline void DFT(vector<int> &a, int opt)
{
int n = a.size(), g = opt == 1 ? 3 : inv3;
for (int i = 0; i < n; ++i)
if (i < rev[i])
std::swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1)
{
int w = quick_pow(g, (mod - 1) / (k << 1));
tw[0] = 1;
for (int j = 1; j < k; ++j)
tw[j] = 1ll * tw[j - 1] * w % mod;
for (int i = 0; i < n; i += k << 1)
{
for (int j = 0; j < k; ++j)
{
int u = a[i + j],
v = 1ll * tw[j] * a[i + j + k] % mod;
add(a[i + j] = u, v);
dec(a[i + j + k] = u, v);
}
}
}
if (opt == -1)
{
int inv_n = quick_pow(n, mod - 2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * inv_n % mod;
}
}
inline void polyMul(vector<int> &a, vector<int> b)
{
if (!a.size() || !b.size())
{
a.clear();
return ;
}
int m = 0, _n = a.size() + b.size() - 2, n;
for (n = 1; n <= _n; n <<= 1)
++m;
a.resize(n);
b.resize(n);
for (int i = 1; i < n; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (m - 1));
DFT(a, 1); DFT(b, 1);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * b[i] % mod;
DFT(a, -1);
a.resize(_n + 1);
}
inline void solve(int l, int r)
{
if (l == r)
return ;
int mid = l + r >> 1;
solve(l, mid);
vector<int> a(mid - l + 1), b(r - l + 1);
for (int i = l; i <= mid; ++i)
a[i - l] = f[i];
for (int i = 0; i < r - l + 1; ++i)
b[i] = g[i];
polyMul(a, b);
for (int i = mid + 1; i <= r; ++i)
add(f[i], a[i - l]);
solve(mid + 1, r);
}
inline void polyDer(vector<int> &a)
{
int n = a.size();
for (int i = 0; i < n; ++i)
a[i] = 1ll * (i + 1) * a[i + 1] % mod;
a.pop_back();
}
inline void polyInt(vector<int> &a)
{
int n = a.size();
a.push_back(0);
for (int i = n - 1; i >= 0; --i)
a[i + 1] = 1ll * inv[i + 1] * a[i] % mod;
a[0] = 0;
}
inline void polyInv(vector<int> a, vector<int> &b)
{
int n = a.size();
b.push_back(quick_pow(a[0], mod - 2));
vector<int> c;
int m = 1, m2, k;
while (m < n)
{
m <<= 1, k = 0;
for (m2 = 1; m2 < (m << 1); m2 <<= 1)
++k;
for (int i = 1; i < m2; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (k - 1));
b.resize(m2); c.resize(m2);
for (int i = 0; i < m2; ++i)
c[i] = i < n && i < m ? a[i] : 0;
DFT(b, 1); DFT(c, 1);
for (int i = 0; i < m2; ++i)
{
int tmp = b[i];
add(b[i], tmp);
dec(b[i], 1ll * c[i] * tmp % mod * tmp % mod);
}
DFT(b, -1);
b.resize(m);
}
b.resize(n);
}
inline void polyLn(vector<int> a, vector<int> &b)
{
int n = a.size();
polyInv(a, b);
polyDer(a);
polyMul(b, a);
b.resize(n - 1);
polyInt(b);
}
inline void polyExp(vector<int> a, vector<int> &b)
{
int n = a.size();
b.push_back(1);
vector<int> c;
int m = 1;
while (m < n)
{
m <<= 1;
c.clear();
b.resize(m);
polyLn(b, c);
for (int i = 0; i < m; ++i)
add(c[i] = mod - c[i], i < n ? a[i] : 0);
add(c[0], 1);
polyMul(b, c);
b.resize(m);
}
b.resize(n);
}
inline void polyPow(vector<int> &a, int k1, int k2)
{
int n = a.size(), st = 0;
while (st < n && !a[st])
++st;
for (int i = st; i < n; ++i)
a[i - st] = a[i];
for (int i = n - st; i < n; ++i)
a[i] = 0;
a.resize(n);
int a0 = a[0], inv_a0 = quick_pow(a0, mod - 2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * inv_a0 % mod;
vector<int> b;
polyLn(a, b);
for (int i = 0; i < n; ++i)
b[i] = 1ll * b[i] * k1 % mod;
a.clear();
polyExp(b, a);
a.resize(n);
a0 = quick_pow(a0, k2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * a0 % mod;
for (int i = n - 1; i >= 0; --i)
if (i + 1ll * st * k1 < n)
a[i + 1ll * st * k1] = a[i];
for (int i = 0, im = Min((ll)n, 1ll * st * k1); i < im; ++i)
a[i] = 0;
}
inline void polySqrt(vector<int> a, vector<int> &b)
{
int n = a.size();
b.push_back(1);
vector<int> c, d;
int m = 1;
while (m < n)
{
m <<= 1;
b.resize(m);
c.clear();
polyInv(b, c);
d.resize(m);
for (int i = 0; i < m; ++i)
d[i] = i < n ? a[i] : 0;
polyMul(c, d);
for (int i = 0; i < m; ++i)
b[i] = 1ll * inv2 * (c[i] + b[i]) % mod;
}
b.resize(n);
}
生成函数
OGF
- 即一般生成函数 A ( x ) = ∑ i ≥ 0 a i x i A(x) = \sum \limits_{i \ge 0}a_ix^i A(x)=i≥0∑aixi。
- 常用公式: 1 1 − x = ∑ i ≥ 0 x i , 1 ( 1 − x ) 2 = ∑ i ≥ 0 ( i + 1 ) x i , 1 ( 1 − a x ) m = ∑ i ≥ 0 ( i + m − 1 m − 1 ) a i x i \frac{1}{1 - x} = \sum \limits_{i\ge 0}x^i, \frac{1}{(1-x)^2} = \sum \limits_{i \ge 0}(i + 1)x^i,\frac{1}{(1 - ax)^m} = \sum \limits_{i\ge 0}\binom{i + m - 1}{m - 1}a^ix^i 1−x1=i≥0∑xi,(1−x)21=i≥0∑(i+1)xi,(1−ax)m1=i≥0∑(m−1i+m−1)aixi
- 推导上述公式尽量用消项法或组合意义,求导/积分则较为麻烦。
- 结论 1 1 − y ( y 1 − y ) k = ∑ n ≥ 0 ( n k ) y n \dfrac{1}{1 - y}\left(\dfrac{y}{1 - y}\right)^k = \sum \limits_{n \ge 0}\binom{n}{k}y^n 1−y1(1−yy)k=n≥0∑(kn)yn
证明
A ( x , y ) = ∑ k ≥ 0 ( ∑ n ≥ 0 ( n k ) y n ) x k = ∑ n ≥ 0 ∑ k ≥ 0 ( n k ) x k y n = ∑ n ≥ 0 [ ( 1 + x ) y ] n = 1 1 − y − x y = 1 1 − y 1 1 − y 1 − y x = ∑ k ≥ 0 1 1 − y ( y 1 − y ) k x k \begin{aligned} A(x, y) &= \sum \limits_{k \ge 0} \left(\sum \limits_{n \ge 0}\binom{n}{k}y^n\right) x^k\\ &= \sum \limits_{n \ge 0}\sum \limits_{k \ge 0} \binom{n}{k} x^k y^n \\ &= \sum \limits_{n \ge 0} [(1 + x)y]^n \\ &= \dfrac{1}{1 - y - xy}\\ &= \dfrac{1}{1 - y} \dfrac{1}{1 - \dfrac{y}{1 - y}x}\\ &= \sum \limits_{k\ge 0}\dfrac{1}{1 - y}\left(\dfrac{y}{1 - y}\right)^kx^k\\ \end{aligned} A(x,y)=k≥0∑(n≥0∑(kn)yn)xk=n≥0∑k≥0∑(kn)xkyn=n≥0∑[(1+x)y]n=1−y−xy1=1−y11−1−yyx1=k≥0∑1−y1(1−yy)kxk
典例 Bobo String Count
题目大意
- 给定一个 01 串 t t t,求在长度为 n n n 且 t t t 恰好出现 k k k 次的 01 串个数,答案对 998244353 取模。
- 0 ≤ k ≤ n 0 \le k \le n 0≤k≤n, 1 ≤ n , ∣ t ∣ ≤ 5 × 1 0 4 1 \le n, |t| \le 5 \times 10^4 1≤n,∣t∣≤5×104。
解法
- 令 m = ∣ t ∣ m = |t| m=∣t∣,设 g i g_i gi 表示长度为 i + m i + m i+m 的字符串长度为 m m m 的前后缀均为 t t t 的方案数,则:
- 若 i < m i < m i<m,若 m − i m - i m−i 是 t t t 的 border,则 g i g_i gi 为 1,否则为 0。
- 若 i ≥ m i \ge m i≥m,则 g i = 2 i − m g_i = 2^{i - m} gi=2i−m。
- 设 h i h_i hi 表示长度为 i + m i + m i+m 的字符串长度为 m m m 的前后缀均为 t t t 且中间不再有 t t t 出现的方案数,则根据容斥原理有:
h i = g i − ∑ j = 1 i − 1 h j g i − j h_i = g_i - \sum\limits_{j = 1}^{i - 1}h_jg_{i - j} hi=gi−j=1∑i−1hjgi−j
- 设 H ( x ) = ∑ i ≥ 1 h i x i H(x) = \sum\limits_{i \ge 1}h_ix^i H(x)=i≥1∑hixi, G ( x ) = ∑ i ≥ 0 g i x i G(x) = \sum\limits_{i\ge 0}g_ix^i G(x)=i≥0∑gixi,则有:
G = H G + 1 ⇔ H = 1 − 1 G G = HG + 1 \Leftrightarrow H = 1 - \dfrac{1}{G} G=HG+1⇔H=1−G1
- 设 p i p_i pi 表示长度为 i + m i + m i+m 的字符串前(后)缀为 t t t 的方案数,同样根据容斥原理有:
p i = 2 i − ∑ j = 1 i h j 2 i − j p_i = 2^i - \sum \limits_{j = 1}^{i}h_j2^{i-j} pi=2i−j=1∑ihj2i−j
- 设 P ( x ) = ∑ i ≥ 0 p i x i P(x) = \sum \limits_{i \ge 0}p_ix^i P(x)=i≥0∑pixi,讨论 k k k:
- 若 k = 0 k = 0 k=0,则答案为 2 n − ∑ j = 0 n − m p j 2 n − m − j 2^n - \sum \limits_{j = 0}^{n- m}p_j2^{n - m - j} 2n−j=0∑n−mpj2n−m−j,时间复杂度 O ( n log n ) \mathcal O(n\log n) O(nlogn)。
- 若 k > 0 k > 0 k>0,则答案为 [ x n − m ] P 2 H k − 1 [x^{n - m}]P^2H^{k - 1} [xn−m]P2Hk−1,时间复杂度 O ( n log n log k ) \mathcal O(n \log n \log k) O(nlognlogk)。
EGF
-
即指数型生成函数 A ( x ) = ∑ i ≥ 0 a i i ! x i A(x) = \sum \limits_{i \ge 0}\frac{a_i}{i!}x^i A(x)=i≥0∑i!aixi。
-
若将 B B B 划分为若干个非空无序集合 A A A,则 B B B 与 A A A 的 E G F \mathbb{EGF} EGF 的关系为( A ( x ) A(x) A(x) 常数项必须为 0, B ( x ) B(x) B(x) 常数项必须为 1):
B ( x ) = ∑ i ≥ 0 A ( x ) i i ! = e A ( x ) , A ( x ) = ln B ( x ) B(x) = \sum \limits_{i \ge 0}\frac{A(x)^i}{i!} = e^{A(x)},A(x) = \ln B(x) B(x)=i≥0∑i!A(x)i=eA(x),A(x)=lnB(x)
- 例如若 B B B 为排列, A A A 为置换,则
B ( x ) = ∑ i ≥ 0 i ! i ! x i = ∑ i ≥ 0 x i = 1 1 − x A ( x ) = ∑ i ≥ 1 ( i − 1 ) ! i ! x i = ∑ i ≥ 1 x i i = ln 1 1 − x B(x) = \sum \limits_{i \ge 0} \dfrac{i!}{i!}x^i = \sum \limits_{i \ge 0}x^i = \dfrac{1}{1 - x} \\ A(x) = \sum \limits_{i \ge 1} \dfrac{(i - 1)!}{i!}x^i = \sum \limits_{i \ge 1}\dfrac{x^i}{i} = \ln \dfrac{1}{1 - x} \\ B(x)=i≥0∑i!i!xi=i≥0∑xi=1−x1A(x)=i≥1∑i!(i−1)!xi=i≥1∑ixi=ln1−x1
- 常见的例子即可通过 n n n 个点有标号无向图的 E G F \mathbb{EGF} EGF 取 ln \ln ln 得到 n n n 个点有标号无向连通图的 E G F \mathbb{EGF} EGF,对 n n n 个点有标号无向树的 E G F \mathbb{EGF} EGF 取 exp \text{exp} exp 即可得到 n n n 个点有标号无向森林的 E G F \mathbb{EGF} EGF。
典例 CF891E
题目大意
- 有 n n n 个数 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an,要进行 k k k 次操作,每次随机选择一个数 x ∈ [ 1 , n ] x \in [1, n] x∈[1,n],把 a x a_x ax 减 1,将答案增加除 a x a_x ax 外所有数的乘积。
- 求最终答案的期望。
解法
- 设 k k k 次操作后 a i a_i ai 变成了 a i − b i a_i - b_i ai−bi,实际每次操作的贡献是操作前后序列乘积的差,即贡献序列是序列乘积的差分,因而总的贡献就是初始序列的乘积减去最终序列的乘积,即求 ∏ i = 1 n a i − ∏ i = 1 n ( a i − b i ) \prod \limits_{i = 1}^{n}a_i - \prod\limits_{i = 1}^{n}(a_i - b_i) i=1∏nai−i=1∏n(ai−bi) 的期望。
- 显然只要考虑后半部分,用 E G F \mathbb {EGF} EGF 推导所有方案的贡献之和,再除以总方案数即为期望。
F i ( x ) = ∑ j ≥ 0 a i − j j ! x j = ( a i − x ) e x F ( x ) = ∏ i = 1 n F i ( x ) = e n x ∏ i = 1 n ( a i − x ) F_i(x) = \sum\limits_{j \ge 0}\frac{a_i - j}{j!}x^j = (a_i - x)e^x \\ F(x) = \prod\limits_{i = 1}^{n}F_i(x) = e^{nx} \prod\limits_{i = 1}^{n}(a_i - x) Fi(x)=j≥0∑j!ai−jxj=(ai−x)exF(x)=i=1∏nFi(x)=enxi=1∏n(ai−x)
- F ( x ) F(x) F(x) 后半部分可以暴力卷积,同时我们只关心 [ x k ] F ( x ) [x^k]F(x) [xk]F(x), 将前半部分展开后直接计算即可。
常见技巧
- 将生成函数 A ( x ) A(x) A(x) 乘上 1 1 − x \frac{1}{1-x} 1−x1 相当于乘上 ∑ i ≥ 0 x i \sum \limits_{i\ge0}x^i i≥0∑xi, 实际上就是对 A ( x ) A(x) A(x) 的系数求前缀和,乘 ( 1 1 − x ) k (\frac{1}{1-x})^k (1−x1)k 即求 k k k 次前缀和,参见组合数学高阶前缀和部分。