Algorithm Review 6 多项式

多项式

拉格朗日插值

  • 已知最高次数不超过 n − 1 n - 1 n1 的多项式在平面上的 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 ∀1i<jn,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=1nyij=ixixjxxj
  • 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=1n(xi))(i=1n(xi)(i1)!(ni)!(1)niyi)
  • 预处理相关逆元和前缀积,即可 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 n1 y y y 的最高次数不超过 m − 1 m - 1 m1,则可还原出多项式:
    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=1nj=1mzijk=ixijxkjxxkjl=jxijxilxxil

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,,ωnn1 的点值,将点值相乘再通过类似的过程还原回多项式,即可快速求出多项式 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=0n1akxk=k=02n1(a2kx2k+a2k+1x2k+1)=k=02n1a2k(x2)k+xk=02n1a2k+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=02n1a2kxk,A2(x)=i=02n1a2k+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),0k2n1,则
    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),0kn1

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(ωnn1)0(ωn0)1(ωn1)1(ωnn1)1(ωn0)n1(ωn1)n1(ωnn1)n1 a0a1an1 = A(ωn0)A(ωn1)A(ωnn1)
  • 对矩阵求逆,可得:
    [ 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] a0a1an1 =n1 (ωn0)0(ωn1))0(ωn(n1))0(ωn0)1(ωn1)1(ωn(n1))1(ωn0)n1(ωn1)n1(ωn(n1))n1 A(ωn0)A(ωn1)A(ωnn1)
  • 不难发现,将 ω n k \omega_n^k ωnk 替换成 ω n − k \omega_n^{-k} ωnk 再做一遍 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; //相乘的两个多项式最高次数分别为 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));
  • 最终我们得到了 FFT 的迭代实现。
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)P11(modP),g2P11(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} g2xma 替换 ω 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} g2xma 的幂次,减小常数,代码见多项式基本操作。

多项式基本操作

  • 相关数组应开到题目给定长度的四倍,线性逆元应预处理到题目给定长度的两倍。

快速卷积的变式

  • 欲求 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=0nkfigi+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 gnik=gi+k,hnk=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} hnk=i=0nkfignki
  • 做快速卷积即可。

分治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=1ifijgj(1i<n) 为例, g 1 , g 2 , … , g n − 1 g_1, g_2, \dots, g_{n - 1} g1,g2,,gn1 已知, 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,,grl 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}) \\ 0g(f0)+g(f0)(ff0)(modx2n)ff0g(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]20(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)[1lnB0(x)+A(x)](modx2n)
  • 需保证 A ( x ) A(x) A(x) 的常数项为 0。

多项式开根

  • 套用牛顿迭代,令 g ( x ) = x 2 − A g(x) = x^2 - A g(x)=x2A,最终要使 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)=P1 取模。
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;
}

//以下各函数 vector &b 在传入之前需保证为空 

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);
}

/*
polyInv's example:
5
1 6 3 4 9
1 998244347 33 998244169 1020

polyLn's example:
6
1 927384623 878326372 3882 273455637 998233543
0 927384623 817976920 427326948 149643566 610586717 

polyExp's example:
reversed
*/

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);
}

// 调用该函数前应先特判因前若干项为 0 导致结果全为 0 的情况 
// k1 是指数对 mod 取模后的值, k2 是指数对 mod - 1 取模后的值
// 指数较小时两者可合并
 
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)=i0aixi
  • 常用公式: 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 1x1=i0xi,(1x)21=i0(i+1)xi,(1ax)m1=i0(m1i+m1)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 1y1(1yy)k=n0(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)=k0(n0(kn)yn)xk=n0k0(kn)xkyn=n0[(1+x)y]n=1yxy1=1y111yyx1=k01y1(1yy)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 0kn 1 ≤ n , ∣ t ∣ ≤ 5 × 1 0 4 1 \le n, |t| \le 5 \times 10^4 1n,t5×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 mi t t t 的 border,则 g i g_i gi 为 1,否则为 0。
    • i ≥ m i \ge m im,则 g i = 2 i − m g_i = 2^{i - m} gi=2im
  • 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=gij=1i1hjgij
  • H ( x ) = ∑ i ≥ 1 h i x i H(x) = \sum\limits_{i \ge 1}h_ix^i H(x)=i1hixi G ( x ) = ∑ i ≥ 0 g i x i G(x) = \sum\limits_{i\ge 0}g_ix^i G(x)=i0gixi,则有:
    G = H G + 1 ⇔ H = 1 − 1 G G = HG + 1 \Leftrightarrow H = 1 - \dfrac{1}{G} G=HG+1H=1G1
  • 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=2ij=1ihj2ij
  • P ( x ) = ∑ i ≥ 0 p i x i P(x) = \sum \limits_{i \ge 0}p_ix^i P(x)=i0pixi,讨论 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} 2nj=0nmpj2nmj,时间复杂度 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} [xnm]P2Hk1,时间复杂度 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)=i0i!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)=i0i!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)=i0i!i!xi=i0xi=1x1A(x)=i1i!(i1)!xi=i1ixi=ln1x1
    • 常见的例子即可通过 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 aibi,实际每次操作的贡献是操作前后序列乘积的差,即贡献序列是序列乘积的差分,因而总的贡献就是初始序列的乘积减去最终序列的乘积,即求 ∏ 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=1naii=1n(aibi) 的期望。
  • 显然只要考虑后半部分,用 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)=j0j!aijxj=(aix)exF(x)=i=1nFi(x)=enxi=1n(aix)

  • 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} 1x1 相当于乘上 ∑ i ≥ 0 x i \sum \limits_{i\ge0}x^i i0xi, 实际上就是对 A ( x ) A(x) A(x) 的系数求前缀和,乘 ( 1 1 − x ) k (\frac{1}{1-x})^k (1x1)k 即求 k k k 次前缀和,参见组合数学高阶前缀和部分。

你可能感兴趣的:(学习笔记,多项式,生成函数)