多项式全家桶

前言

多项式乱七八糟的公式和做法实在是太多了,有点遭不住,写一个学习笔记,记录一下多项式的各种奇奇怪怪的模板。

多项式乘法

系数表示法

即用这个多项式的每一项系数来表示这个多项式。

对于一个 \(n−1\)\(n\) 项多项式:

\[f(x)=\sum_{i=0}^{n−1}a_ix_i \]

用系数表示法为:\(f(x)=\{a_0,a_1 \dots a_i \dots a_{n−1}\}\)

点值表示法

所以,对于一个 \(n−1\) 次多项式,我们可以用 \(n\) 个点值来表示这个多项式。

即, \(f(x)=\{(x_0,f(x_0)),(x_1,f(x_1)) \dots (x_i,f(x_i)) \dots (x_{n−1},f(x_{n−1}))\}\)

点值表示法的重要优势在于,我们可以通过简单的算术运算来实现多项式的运算,举个例子:

\[f(x) · g(x)=\{(x_0,f(x_0)·g(x_0)),(x_1,f(x_1)·g(x_1)) \dots (x_i,f(x_i)·g(x_i)) \dots (x_{n−1},f(x_{n−1})·g(x_{n-1}))\} \]

单位根

将单位圆上的点所代表的复数叫作单位根,用 \(\omega^k_n\) 表示,\(n\) 表示圆分成的份数,且通常是 \(2\) 的次幂,\(k\) 表示逆时针数第 \(k\) 个点。

单位根有如下几个性质:

\[\omega^k_n = \cos{\frac {k} {n}} + \sin{\frac{k}{n}}\mathrm{i} \]

\[(\omega^1_n)^k = \omega^k_n \]

\[\omega^{2k}_{2n} = \omega^{k}_{n} \]

\[\omega^{k + \frac{2}{n}}_{n} = - \omega^{k}_{n} \]

FFT

\[\begin {alignedat}{3} A(x) & = \sum_{i=1}^{n-1}a_ix^i \\ & = (a_0 + a_2x^2 \dots a_{n-2}x^{n-2}) + (a_1x + a_3x^3 \dots a_{n-1}x^{n-1}) \\ & = (a_0 + a_2x^2 \dots a_{n-2}x^{n-2}) + x(a_1 + a_3x^2 \dots a_{n-1}x^{n-2}) \\ & = A_1(x^2) + xA_2(x^2) \\ \end {alignedat}\]

\(k < \frac{n}{2}\)然后将 \(\omega^k_n\) 带入上式, 则有,

\[\begin {alignedat}{3} A_1(x^2) + xA_2(x^2) & = A_1((\omega^{k}_{n})^2) + xA_2((\omega^{k}_{n})^2)\\ & = A_1(\omega^{2k}_{n}) + \omega^{k}_{n}A_2(\omega^{2k}_{n}) \\ \end {alignedat}\]

然后我们再将 \(\omega^{k + \frac{n}{2}}_n\) 带入上式, 则有,

\[\begin {alignedat}{3} A_1(x^2) + xA_2(x^2) & = A_1((\omega^{k + \frac{n}{2}}_n)^2) + xA_2((\omega^{k + \frac{n}{2}}_n)^2)\\ & = A_1(\omega^{2k + n}_n) + \omega^{k + \frac{n}{2}}_nA_2(\omega^{2k + n}_n) \\ & = A_1(\omega^{2k}_n) - \omega^{k}_nA_2(\omega^{2k}_n) \\ \end {alignedat}\]

因为有以上的性质存在,我们只需要求出 \(A_1(\omega^{k}_{\frac{n}{2}})\)\(A_2(\omega^{k}_{\frac{n}{2}})\) 的值,就可以求出 \(A (\omega^{k}_n)\)\(A (\omega^{k + \frac{n}{2}}_n)\) 的值。

因此可以倍增的将系数表达式转化成点值表达式,所需要的时间复杂度为 \(O(n\log{n})\)

IFFT

有这样一个结论,证明略。

一个多项式在分治的过程中乘上单位根的共轭复数,分治完的每一项除以 \(n\) 即为原多项式的每一项系数。

因此我们只需要在 FFT 中 \(\omega\) 的虚部乘上一个复数, 然后最终结果除以 \(n\) 即可

迭代优化:倍增过程中迭代的效率太差了,可以发现,分治后的位置,是其下标二进制翻转以后的位置,我们可以先预处理好它最后到达的位置,在 FFT 之前交换即可。

FFT实现代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long lld;

const int N = 1e6 + 50;
const double PI = acos (-1.0);

struct Complex {
	double x, y;
	Complex (register double xx = 0, register double yy = 0) {
		x = xx, y = yy;
	}
	inline Complex operator + (const Complex &a) const {
		return Complex (a.x + x, a.y + y);
	}
	inline Complex operator - (const Complex &a) const {
		return Complex (x - a.x, y - a.y);
	}
	inline Complex operator * (const Complex &a) const {
		return Complex (x * a.x - y * a.y, x * a.y + y * a.x);
	}
} a[N << 2], b[N << 2];

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n, m;

int limit = 1;
int l, r[N << 2];
inline void FFT (register Complex * A, register int type) {
	for (register int i = 0; i < limit; i ++)
		if (i < r[i])  swap (A[i], A[r[i]]);
	for (register int mid = 1; mid < limit; mid <<= 1) {
		register Complex Wn (cos (PI / mid), type * sin (PI / mid));
		for (register int R = mid << 1, j = 0; j < limit; j += R) {
			register Complex w (1, 0);
			for (register int k = 0; k < mid; k ++, w = w * Wn) {
				register Complex x = A[j + k], y = w * A[j + mid + k];
				A[j + k] = x + y;
				A[mid + j + k] = x - y;
			}
		}
	}
}

int main () {
	n = read(), m = read();
	for (register int i = 0; i <= n; i ++)  a[i].x = read();
	for (register int i = 0; i <= m; i ++)  b[i].x = read();
	while (limit <= n + m)  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
	FFT (a, 1);
	FFT (b, 1);
	for (register int i = 0; i <= limit; i ++)  a[i] = a[i] * b[i];
	FFT (a, -1);
	for (register int i = 0; i <= n + m; i ++)  printf ("%d%c", (int)(a[i].x / limit + 0.5), i == limit - 1 ? '\n' : ' ');;
	return 0;	
}

NTT

因为 FFT 涉及到了 double, 精度堪忧, 所以考虑寻找一种不需要使用 double 数据类型可以解决的多项式转化点值表达式方法,那么需要找到一个合适的东西来代替单位根,下面的只涉及到定义,而跟具体内容无关。

\(a,p\)为 整数,且 \(\gcd{(a,p)} = 1\) ,
使 \(a^n \equiv 1 \pmod{p}\) 成立的最小正整数 \(n\) 叫做 \(a\)\(p\) 的阶,记作 \(\delta_p(a) = n\)

\(p\) 为正整数, \(a\) 为整数, 若 \(a\)\(p\) 的阶等于 \(\varphi(p)\) ,即 \(\delta_p(a) = \varphi(p)\), 则称 \(a\)\(m\) 的一个原根。

对于一个模数 \(p\) ,设他的原根为 \(g\) ,则有,

\[g^{\varphi(p)} \equiv 1 \pmod{p} \]

\(p\) 为质数, 则有 \(g^{p - 1} \equiv 1 \pmod{p}\), 根据单位根的性质, 有

\[(\omega^1_n)^n \equiv g^{p - 1} \equiv 1 \pmod{p} \]

\[\omega^1_n \equiv g^{\frac{p - 1}{n} } \pmod{p} \]

\[\omega^{-1}_n \equiv g^{-\frac{p - 1}{n} } \pmod{p} \]

因此,可以使用原根来代替单位根,从而在取模意义下避过了 double 的精度限制, 从而具有更好的精度表现

NTT实现代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long lld;

const int N = 1e6 + 50;
const int mod = 998244353;

int a[N << 2], b[N << 2];

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n, m;

int limit = 1;
int l, r[N << 2];

inline int qpow (register int a, register int b) {
	register int base = 1;
	while (b) {
		if (b & 1)  base = 1ll * base * a % mod;
		a = 1ll * a * a % mod;
		b >>= 1;
	}
	return base;
}

inline void NTT (register int * A, register int type) {
	for (register int i = 0; i < limit; i ++)
		if (i < r[i])  swap (A[i], A[r[i]]);
	for (register int mid = 1; mid < limit; mid <<= 1) {
		register int Gn = qpow (type, (mod - 1) / (mid << 1));
		for (register int R = mid << 1, j = 0; j < limit; j += R) {
			register int g = 1;
			for (register int k = 0; k < mid; k ++, g = 1ll * g * Gn % mod) {
				register int x = A[j + k], y = 1ll * g * A[j + mid + k] % mod;
				A[j + k] = (x + y) % mod;
				A[mid + j + k] = (x - y + mod) % mod;
			}
		}
	}
}

int main () {
	n = read(), m = read();
	for (register int i = 0; i <= n; i ++)  a[i] = read();
	for (register int i = 0; i <= m; i ++)  b[i] = read();
	while (limit <= n + m)  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
	NTT (a, 3);
	NTT (b, 3);
	for (register int i = 0; i <= limit; i ++)  a[i] = 1ll * a[i] * b[i] % mod;
	NTT (a, qpow (3, mod - 2));
	for (register int i = 0; i <= n + m; i ++)  printf ("%lld%c", 1ll * a[i] * qpow (limit, mod - 2) % mod, i == limit - 1 ? '\n' : ' ');;
	return 0;	
}

任意模数多项式乘法

求解 \(F(x) · G(x) \pmod {p}\)

考虑到 NTT 无法实现对任意模数的取模运算, 考虑使用 FFT, 主要问题是值域极大,精度问题非常严峻

可以从值域入手,将值域缩小为,

\[F(x) = M \times A(x) + B(x) \]

\[G(x) = M \times C(x) + D(x) \]

当我们的 \(M\) 取到 \(2^{15}\) 时,可以有效地解决精度危机,那问题转化为:

\[(M \times A + B) \times (M \times C + D) \]

\[= M^2 A C + M (BC + AD) + BD \]

然后可以使用 \(7\) 次 FFT 解决问题

多项式乘法逆

给定 \(F(x)\) , 求 \(G(x)\) 满足 \(F · G \equiv 1 \pmod{x^n}\), 多项式系数对 \(998244353\) 取模

假设已经有 \(G' \equiv F \pmod{x^{\lceil \frac {n}{2}\rceil}}\)

则有 \(G' - G \equiv 0 \pmod{x^{\lceil \frac {n}{2}\rceil}}\)

两边同时平方,得 \((G' - G)^2 \equiv 0 \pmod {x^n}\)

即, \({G'}^2 - 2G'G + G^2 \equiv 0 \pmod {x^n}\)

两边同乘 \(F(x)\) ,则 \(F{G'}^2 - 2G'GF + G^2 F \equiv 0 \pmod {x^n}\)

因为\(FG \equiv 1 \pmod {x^n}\), 则\(F{G'}^2 - 2G' + G^2 F \equiv 0 \pmod {x^n}\)

最终则有, \(G \equiv 2G' - F{G'}^2 \pmod {x^n}\)

最后递归求解即可

int res[N << 2];
inline void Poly_Inv (register int len, register int * a, register int * b) {
	if (len == 1)  return b[0] = qpow (a[0], mod - 2), void ();
	Poly_Inv ((len + 1) >> 1, a, b);
	register int limit = 1, l = 0;
	while (limit <= (len << 1))  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  res[i] = 0, r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
	for (register int i = 0; i < len; i ++)  res[i] = a[i];
	NTT (limit, res, 3), NTT (limit, b, 3);
	for (register int i = 0; i < limit; i ++)  b[i] = ((2ll * b[i] % mod - 1ll * res[i] * b[i] % mod * b[i] % mod) % mod + mod) % mod;
	NTT (limit, b, inv3);
	register int inv = qpow (limit, mod - 2);
	for (register int i = 0; i < len; i ++)  b[i] = 1ll * b[i] * inv % mod;
	for (register int i = len; i < limit; i ++)  b[i] = 0;
}

多项式除法

给定一个 \(n\) 次多项式 \(F(x)\) 和一个 \(m\) 次多项式 \(G(x)\), 求 \(A(x), B(x)\) 满足 \(F = AG + B\)

考虑到目前可以解决乘法逆的问题,考虑写成一个同余式,其中有乘法逆来求解,那么需要构建一个 \(x\) 的若干次幂作为模数

\[F(\frac{1}{x}) = A(\frac{1}{x})G(\frac{1}{x}) + B(\frac{1}{x}) \]

同乘 \(x^n\)

\[x^nF(\frac{1}{x}) = x^{n - m}A(\frac{1}{x})x^mG(\frac{1}{x}) + x^{n - m + 1}x^{m - 1}B(\frac{1}{x}) \]

\(A_R(x) = x^nA(\frac{1}{x})\), 那么发现 \(A_R(x)\) 实际上是有 \(A(x)\) 系数反转得到

\[F_R(x) = A_R(x)G_R(x) + x^{n - m + 1}B_R(x) \]

\[F_R(x) \equiv A_R(x)G_R(x) \pmod{x^{n - m + 1}} \]

\[A_R \equiv F_RG_R^{-1} \pmod{x^{n - m + 1}} \]

可以求出 \(A(x)\) ,代入原式,

\[B = F - AG \]

int inv_gr[N << 2];
int A[N << 2];
inline void Poly_Div () {
	Poly_Inv (n - m + 1, gr, inv_gr);
	register int limit = 1, l = 0;
	while (limit <= 2 * n - m + 1)  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
	NTT (limit, fr, 3), NTT (limit, inv_gr, 3);
	for (register int i = 0; i < limit; i ++)  A[i] = 1ll * fr[i] * inv_gr[i] % mod;
	NTT (limit, A, inv3);
	register int inv = qpow (limit, mod - 2);
	for (register int i = n - m + 1; i < limit; i ++)  A[i] = 0;
	for (register int i = n - m; i >= 0; i --)  printf ("%d ", A[i] = 1ll * A[i] * inv % mod);
	reverse (A, A + 1 + n - m);
	return putchar ('\n'), void ();
}
int B[N << 2];
inline void Poly_Rem () {
	register int limit = 1, l = 0;
	while (limit <= 2 * n - m + 1)  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
	NTT (limit, A, 3), NTT (limit, g, 3);
	for (register int i = 0; i < limit; i ++)  B[i] = 1ll * A[i] * g[i] % mod;
	NTT (limit, B, inv3);
	register int inv = qpow (limit, mod - 2);
	for (register int i = 0; i <= m - 1; i ++)  B[i] = 1ll * B[i] * inv % mod;
	for (register int i = 0; i <= m - 1; i ++)  printf ("%d ", (f[i] - B[i] + mod) % mod);
	return putchar ('\n'), void ();
}

FWT

给定两个序列 \(A, B\),求 \(C\) 满足 \(C_i = \sum_{j \oplus k = i}a_j \times b_k\)
其中, \(\oplus\) 可以为AND、OR、XOR等。

考虑与 FFT 类似的算法去解决问题,分治解决。

已知 \(c = a \oplus_{xor} b\) ,设三个序列按照奇偶分段后分别有 \(a_0, a_1, b_0, b_1, c_0, c_1\),则有

\[c_0 = (a_0 \oplus_{xor} b_0) + (a_1 \oplus_{xor} b_1) \]

\[c_1 = (a_0 \oplus_{xor} b_1) + (a_1 \oplus_{xor} b_0) \]

其中 \(+\) 表示对应位置相加,类似于向量加法。

考虑蝴蝶变换,发现

\[(a_0 + a_1) \oplus_{xor} (b_0 + b_1) = (a_0 \oplus_{xor} b_0) + (a_1 \oplus_{xor} b_1) + (a_1 \oplus_{xor} b_0) + (a_0 \oplus_{xor} b_1) \]

\[(a_0 - a_1) \oplus_{xor} (b_0 - b_1) = (a_0 \oplus_{xor} b_0) + (a_1 \oplus_{xor} b_1) - (a_1 \oplus_{xor} b_0) - (a_0 \oplus_{xor} b_1) \]

对上述两式加减,便可以求得 \(c_0\)\(c_1\) 的值,也就是说,

\[a_0 \Rightarrow a_0 + a_1 \]

\[a_1 \Rightarrow a_0 - a_1 \]

\[b_0 \Rightarrow b_0 + b_1 \]

\[b_1 \Rightarrow b_0 - b_1 \]

这样的话就满足了 FFT 的性质,我们便可以通过倍增求解。

对于 AND 和 OR 的运算,性质同理可得,结论如下,

\[a_0 \Rightarrow_{and} a_0 + a_1 \]

\[a_1 \Rightarrow_{and} a_1 \]

\[a_0 \Rightarrow_{or} a_0 \]

\[a_1 \Rightarrow_{or} a_1 + a_0 \]

FWT实现代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 2e5 + 50, mod = 998244353, inv2 = 499122177;

inline int read () {
	register int x = 0, w = 1;
	register char ch = getchar ();
	for (; ch < '0' || ch > '9'; ch = getchar ()) if (ch == '-') w = -1;
	for (; ch >= '0' && ch <= '9'; ch = getchar ()) x = x * 10 + ch - '0';
	return x * w;
}

int n;
int a[N << 2], b[N << 2], f[N << 2], g[N << 2];

inline int Addmod (register int x, register int y) {
	x += y;
	if (x < 0)  x += mod;
	if (x >= mod)  x -= mod;
	return x;
}
inline int Mulmod (register int x, register int y) {
	x = 1ll * x * y % mod;
	if (x < 0)  x += mod;
	return x;
}

inline void OR (register int * A, register int typ) {
	for (register int len = 2, mid = 1; len <= n; len <<= 1, mid <<= 1)
		for (register int i = 0; i < n; i += len)
			for (register int j = 0; j < mid; j ++)
				A[i + j + mid] = Addmod (A[i + j + mid], Mulmod (typ, A[i + j]));
}

inline void AND (register int * A, register int typ) {
	for (register int len = 2, mid = 1; len <= n; len <<= 1, mid <<= 1)
		for (register int i = 0; i < n; i += len)
			for (register int j = 0; j < mid; j ++)
				A[i + j] = Addmod (A[i + j], Mulmod (typ, A[i + j + mid]));
}

inline void XOR (register int * A, register int typ) {
	for (register int len = 2, mid = 1; len <= n; len <<= 1, mid <<= 1)
		for (register int i = 0; i < n; i += len)
			for (register int j = 0; j < mid; j ++) {
				A[i + j] = Addmod (A[i + j], A[i + j + mid]);
				A[i + j + mid] = Addmod (Addmod (A[i + j], -A[i + j + mid]), -A[i + j + mid]);
				A[i + j] = Mulmod (A[i + j], typ);
				A[i + j + mid] = Mulmod (A[i + j + mid], typ);
			}
}

int main () {
	n = (1 << read());
	for (register int i = 0; i < n; i ++)  a[i] = read();
	for (register int i = 0; i < n; i ++)  b[i] = read();
	for (register int i = 0; i < n; i ++)  f[i] = a[i], g[i] = b[i];
	OR (f, 1), OR (g, 1);
	for (register int i = 0; i < n; i ++)  f[i] = 1ll * f[i] * g[i] % mod;
	OR (f, mod - 1);
	for (register int i = 0; i < n; i ++)  printf ("%d ", f[i]);
	putchar ('\n');

	for (register int i = 0; i < n; i ++)  f[i] = a[i], g[i] = b[i];
	AND (f, 1), AND (g, 1);
	for (register int i = 0; i < n; i ++)  f[i] = 1ll * f[i] * g[i] % mod;
	AND (f, mod - 1);
	for (register int i = 0; i < n; i ++)  printf ("%d ", f[i]);
	putchar ('\n');

	for (register int i = 0; i < n; i ++)  f[i] = a[i], g[i] = b[i];
	XOR (f, 1), XOR (g, 1);
	for (register int i = 0; i < n; i ++)  f[i] = 1ll * f[i] * g[i] % mod;
	XOR (f, inv2);
	for (register int i = 0; i < n; i ++)  printf ("%d ", f[i]);
	putchar ('\n');
	return 0;
}

生成函数初步

给定 \(g_{0,1\dots n - 1}\),且 \(f_0 = 1 , f_i = \sum_{j = 1}^if_{i - j}g_{j}\),求 \(f_{0,1\dots n - 1}\)

不妨设 \(F(x) = \sum_{i=0}^{\infty}x^if_i, G(x) = \sum_{i=0}^\infty x_ig_i\),且\(g_0=0\)

那么则有 \(F(x)G(x) = \sum_{i=0}^\infty x^i \sum_{j+k=i}f_jg_k = F(x) - f0\)

化简则有 \(F \equiv (1-G)^{-1} \pmod{x^n}\)

套用多项式乘法逆模板即可。

牛顿迭代

给定一个 \(n\) 次多项式 \(F(x)\),求 \(G(x)\) 满足,

\[F(G(x)) \equiv 0 \pmod{x^n} \]

设有特解 \(G_0(x)\)满足\(F(G_0(x)) \equiv 0 \pmod{x^{\lceil \frac{n}{2}\rceil}}\)

根据泰勒展开得,

\[F(G) = \sum_{i=0}^\infty \frac{F^{(n)}(G_0)(G - G_0)^n}{n!} \]

\[\because F(G) \equiv 0 \pmod{x^n} \]

\[\therefore \sum_{i=0}^\infty \frac{F^{(n)}(G_0)(G - G_0)^n}{n!} \equiv 0 \pmod{x^n} \]

\[F(G_0) + F'(G_0)(G-G_0) \equiv 0 \pmod{x^n} \]

\[G \equiv G_0 - \frac{F(G_0)}{F'(G_0)} \pmod{x^n} \]

多项式对数函数

给定一个 \(n\) 次多项式 \(F(x)\), 求 \(\ln{F} \pmod{x^n}\)

记 $G(x) = \ln{F} $

两边同时求导 \(G' = \frac{F'}{F}\)

积分得 \(G = \int G'\, {\rm d}x + A\)

int inv_f[N << 2], rf[N << 2];
inline void Poly_Ln (register int n, register int * f, register int * g) {
	register int limit = 1, l = 0;
	while (limit <= (n << 1))  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  inv_f[i] = rf[i] = 0, r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
	Poly_Inv (n, f, inv_f);
	for (register int i = 0; i <= n; i ++)  rf[i] = 1ll * f[i + 1] * (i + 1) % mod;
	NTT (limit, rf, 3), NTT (limit, inv_f, 3);
	for (register int i = 0; i < limit; i ++)  g[i] = 1ll * rf[i] * inv_f[i] % mod;
	NTT (limit, g, inv3);
	register int inv = qpow (limit, mod - 2);
	for (register int i = 0; i <= n; i ++)  g[i] = 1ll * g[i] * inv % mod;
	for (register int i = n + 1; i < limit; i ++)  g[i] = 0;
	for (register int i = n; i >= 1; i --)  g[i] = 1ll * g[i - 1] * qpow (i, mod - 2) % mod;
	g[0] = 0;
}

多项式指数函数

给定 \(n\) 次多项式 \(F(x)\) ,求 \(e^{F(x)}\pmod{x^n}\)

\(G(x) = e^{F(x)}\)

两边同时取对数 \(\ln{G} - F \equiv 0 \pmod{x^n}\)

将左侧视为一个整体 \(H(G)\),套用多项式牛顿迭代,设 \(\ln{G_0} - F \equiv 0 \pmod{x^{\lceil \frac {n}{2}\rceil}}\)

\[H(G_0) = \ln{G} - F \Rightarrow G = G_0 - \frac{H(G_0)}{H'(G_0)} \]

同时,求导有 \(H'(G_0) = \frac{1}{G_0}\),代入上式,

\[G = G_0 - \frac{\ln{G_0} - F}{\frac{1}{G_0}} \]

\[G = G_0( 1 - \ln{G_0} + F) \]

由此,递归迭代就可以解决。

int ln_f[N << 2];
inline void Poly_Exp (register int len, register int * a, register int * b) {
	if (len == 1)  return b[0] = 1, void ();
	Poly_Exp ((len + 1) >> 1, a, b);
	Poly_Ln (len, b, ln_f);
	register int limit = 1, l = 0;
	while (limit <= (len << 1))  limit <<= 1, l ++;
	for (register int i = 0; i < limit; i ++)  res[i] = 0, r[i] = (r[i >> 1] >> 1) | ((i & 1) << l - 1);
	for (register int i = 0; i <= len; i ++)  res[i] = (a[i] - ln_f[i] + mod) % mod;
	res[0] ++;
	NTT (limit, res, 3), NTT (limit, b, 3);
	for (register int i = 0; i < limit; i ++)  b[i] = 1ll * b[i] * res[i] % mod;
	NTT (limit, b, inv3);
	register int inv = qpow (limit, mod - 2);
	for (register int i = 0; i <= len; i ++)  b[i] = 1ll * b[i] * inv % mod;
}

多项式快速幂弱化版

给定一个 \(n\) 次多项式 \(A(x)\)和一个整数 \(k\), 求\(B(x)\) 满足 \(B \equiv A^k \pmod{x^n}\)
在弱化版中:\(a_0 = 1\)

同取 \(\ln\) 得,\(\ln{B} = k\ln{A}\)

\(B = e^{k\ln{A}}\)

Q : 为何要保证 \(a_0 = 1\)
A : 做 \(\ln\)\(\exp\) 的要求。

\(k \geqslant 10^{10^5}\)

下意识地想到欧拉定理,然后下意识对 \(\varphi(P)\) 取余,然后成功 WA 了,但是为何对 \(P\) 取余可行?

不妨举出一个更一般的式子:

求证 : \(f^p(x) \equiv f(x^p) \pmod{p}\)

在本题中, \(n < p\),那么 \(f^p(x) \equiv a_0 \equiv 1\pmod{x^n}\)

证明考虑 \((a+b)^p \equiv a^p + b^p \pmod {p}\)

\(f(x)\)\(k\) 次多项式,\(a_k\) 为其第 \(k\) 项系数,设 \(f(x) = g(x) + a_kx^k\), 且上述结论对一切 \(k - 1\) 次多项式成立

\[f^p(x) = g^p(x) + a^p_kx^{pk} = g(x^p) + a_kx^{pk} = f(x^p) \]

由数学归纳法可得,上述结论成立。

posted @ 2023-08-01 20:05  A_Big_Jiong  阅读(65)  评论(0)    收藏  举报