多项式

多项式

定义

定义 \(f(x) = \sum_{k = 0} ^ m a_k x ^ k\) 为一个 \(m\) 次(\(m + 1\) 阶)的 多项式,其中 \(m \in [0, +\infty)\)

如果一个和式可以按列相加,则称其为 级数。注意,该和式可以有无穷多项,即 \(\sum_{k = 0} ^ \infty a_k x ^ k\)

如果对于每一项的指数 \(i\) 都满足 \(i \in \mathbb{N}\),则称这种级数为 形式幂级数。所谓形式幂级数,其实就是广义的多项式。

注意,形式幂级数与幂级数不同,我们并不关系幂级数是否收敛和是否有确定的取值。

在下文中,如果不加特殊说明,幂级数均指代形式幂级数。

定义一个 多项式环 为若干个多项式组成的集合 \(S[x]\),其中对于任意的两个多项式 \(P(x), Q(x) \in S[x]\),均满足:

  • 加法封闭性:\(P(x), Q(x) \in S[x] \Rightarrow P(x) + Q(x) \in S[x]\)
  • 乘法封闭性:\(P(x), Q(x) \in S[x] \Rightarrow P(x) \times Q(x) \in S[x]\)
  • 分配律:\(P(x) \times (Q(x) + R(x)) = P(x) \times Q(x) + P(x) \times R(x)\)
  • 其他性质,详细请参考 基础群论

最经典的例子是 \(\mathbb{R}[x]\),代表系数是实数的多项式组成的集合。

推广:定义 形式幂级数环 为若干个幂级数组成的集合 \(\mathbb{R}[[x]]\)

多项式乘法(卷积)

乘法是多项式操作中最重要的操作,得益于 \(\text{FFT}\) 算法,我们可以在 \(O(n \log n)\) 的时间复杂度内完成多项式的乘法。

对于两个多项式 \(f(x) = \sum_{i = 0} ^ n a_i x ^ i\)\(g(x) = \sum_{i = 0} ^ m b_i x ^ i\),规定其乘法为:

\[f(x) \times g(x) = \sum_{i = 0} ^ n \sum_{j = 0} ^ m a_i b_j x ^ {i + j} \]

推广:对于两个幂级数 \(f(x), g(x)\),定义其卷积为:

\[f(x) \times g(x) = \sum_{k = 0} ^ \infty \sum_{i + j = k} a_i b_j x ^ k \]

有关高效计算卷积的相关内容可以参考 FFT 与 NTT

NTT 模板

多项式求导

关于求导的前置知识可以参考 基础微积分

对幂级数求导只具有形式意义,定义幂级数的形式导数为:

\[(\sum_{k = 0} ^ \infty a_k x ^ k)' = \sum_{k = 1} ^ \infty k a_{k} x ^ {k - 1} \]

\(n\) 阶多项式求导需要在模 \(x ^ n\) 的意义下进行。

注:对 \(n\) 阶多项式求导会失去 \([x ^ 0]f(x)\) 的数值,使得有效位数少一位。

多项式积分

关于积分的前置知识可以参考 基础微积分

对幂级数积分只具有形式意义,定义幂级数的形式积分为:

\[\int (\sum_{k = 0} ^ \infty a_k x ^ k) \mathrm{d}x = \sum_{k = 0} ^ \infty \frac{a_{k}}{k + 1} x ^ {k + 1} + C \]

\(n\) 阶多项式积分需要在模 \(x ^ n\) 的意义下进行。

注:对 \(n\) 阶多项式积分一般使用 \(C = 0\) 填充 \([x ^ 0]f(x)\),使得有效位数多一位。

因此,对于一个多项式 \(f(x)\),当且仅当 \([x ^ 0] f(x) = 0\),有:

\[\int f'(x) \mathrm{d}x = f(x) \]

多项式求逆

在普通生成函数中,我们曾经推导出:

\[\frac{1}{1 - x} = \sum_{k = 0} ^ \infty x ^ k \]

这启发我们,多项式的乘法逆元并不一定是多项式,而是幂级数。

因此,对于 \(f(x) = \sum_{i = 0} ^ n a_i x ^ i\),定义其 乘法逆元\(f ^ {-1}(x) = \sum_{i = 0} ^ \infty b_i x ^ i\),满足:\(f(x) \times f ^ {-1}(x) = 1\)

不妨展开上式:

\[\begin{aligned} \sum_{k = 0} ^ \infty \sum_{i + j = k} a_i b_j x ^ k &= 1 \Leftrightarrow \begin{cases} a_0 b_0 = 1 \\ \sum_{i + j = k} a_i b_j = 0 \end{cases} \\ \Rightarrow b_0 = \frac{1}{a_0}, b_k &= \frac{-1}{a_0} \sum_{i = 0} ^ {k - 1} a_i b_{k - i} \end{aligned} \]

注意:当 \(a_0 = 0\) 时,原式不存在逆元。


一般在算法竞赛中,我们会求 \(f(x)\) 在模 \(x ^ n\) 意义下的乘法逆元 \(g(x)\),即 \(f(x) \times g(x) \equiv 1 \pmod{x ^ n}\),目的是把 \(f ^ {-1}(x)\) 限制在至多 \(n\) 阶。

在实现时我们可以把 \(n\) 填充到 \(2\) 的整数次幂,以简化对折半的处理,也使得递推实现更加方便。

求解逆元可以使用递归的思想,而实现时采用递推实现。

假设我们已经求得了 \(f(x) \times g'(x) \equiv 1 \pmod{x ^ \frac{n}{2}}\),而我们希望求出 \(f(x) \times g(x) \equiv 1 \pmod{x ^ n}\),则有如下的变换:

\[\begin{aligned} f(x) \times g'(x) &\equiv 1 \pmod{x ^ \frac{n}{2}} \\ f(x) \times g(x) &\equiv 1 \pmod{x ^ n} \\ \Rightarrow g'(x) - g(x) & \equiv 0 \pmod{x ^ \frac{n}{2}} \\ g'(x) ^ 2 - 2 g(x) g'(x) + g(x) ^ 2 & \equiv 0 \pmod{x ^ \frac{n}{2}} \\ f(x) g'(x) ^ 2 - 2 \cancel{f(x)} \cancel{g(x)} g'(x) + \cancel{f(x)} g(x) \cancel{^ 2} & \equiv 0 \pmod{x ^ n} \\ g(x) &\equiv 2 g'(x) - f(x) g'(x) ^ 2 \pmod{n} \end{aligned} \]

则原式可以通过迭代实现求解 \(f(x)\) 的乘法逆元,时间复杂度为 \(T(n) = T(\frac{n}{2}) + O(n \log n) = O(n \log n)\)

对于使用迭代法实现的函数,在调用时需要注意不能传入自身作为参数,同时需要注意先清空数组再进行迭代

多项式求逆模板

多项式对数函数

对多项式进行对数/指数函数变换都会得到无穷项的幂级数,因此下文的计算默认在模 \(x ^ n\) 意义下。

对于 \(f(x) = \sum_{i = 0} ^ n a_i x ^ i\)\(\ln(f(x))\) 存在必须满足 \(a_0 = 1\)

\(\ln(f(x))\) 作如下变换:

\[\begin{aligned} \frac{\mathrm{d} \ln(f(x))}{\mathrm{d}x} &= \ln'(f(x)) \cdot f'(x) \\ &= \frac{1}{f(x)} \cdot f'(x) \\ &= \frac{f'(x)}{f(x)} \\ \therefore \ln(f(x)) &= \int \frac{\mathrm{d} \ln(f(x))}{\mathrm{d}x} \mathrm{d}x \\ &= \int \frac{f'(x)}{f(x)} \mathrm{d}x \\ \end{aligned} \]

因此我们可以对 \(f(x)\) 求导,求逆,卷积,再积分,时间复杂度为 \(O(n \log n)\)

多项式指数函数

对于 \(f(x) = \sum_{i = 0} ^ n a_i x ^ i\)\(g(x) = \exp(f(x))\) 存在必须满足 \(a_0 = 0\)

求解 \(\exp(F(x))\) 可以使用牛顿迭代法。

牛顿迭代法

原理讲解待补充

\[\begin{aligned} f(x) &= 0 \\ x &= x_0 - \frac{f(x_0)}{f'(x_0)} \\ \end{aligned} \]

推广:

\[\begin{aligned} f(g(x)) &= 0 \\ g(x) &= g_0(x) - \frac{f(g_0(x))}{f'(g_0(x))} \\ \end{aligned} \]


如何使用牛顿迭代法求 \(g(x) = \exp(f(x))\)?首先先对式子做一些变换:

\[\begin{aligned} g(x) &= \exp(f(x)) \\ \ln(g(x)) &= f(x) \\ \ln(g(x)) - f(x) &= 0 \\ h(g(x)) &\leftarrow \ln(g(x)) - f(x) \end{aligned} \]

不妨设 \(g_0(x) \equiv \exp(F(x)) \pmod {x ^ \frac{n}{2}}\) 为上一轮迭代的值,则有:

\[\begin{aligned} g(x) &= g_0(x) - \frac{h(g_0(x))}{h'(g_0(x))} \\ h(g_0(x)) &= \ln(g_0(x)) - f(x) \\ h'(g_0(x)) &= \frac{1}{g_0(x)} \\ \Rightarrow g(x) &= g_0(x) - \frac{\ln(g_0(x)) - f(x)}{\frac{1}{g_0(x)}} \\ g(x) &= g_0(x)(1 - \ln(g_0(x)) + f(x)) \end{aligned} \]

注意,对 \(h(g_0(x))\) 求导时,我们认为自变量是 \(g_0\) 这个函数本身,而不是 \(x\),也就是说,我们把 \(f(x)\) 当作了一个常量。

多项式快速幂

使用多项式 \(\ln\)\(\exp\) 可以容易将多项式幂的问题转化为对系数点乘的问题,需要注意多项式不存在扩展欧拉定理,对指数取模直接对质数本身取模即可。

多项式开根

多项式开根可以转化为多项式 \(\frac{1}{2}\) 次方的问题,对有理数取模即可。

多项式三角函数

多项式 \(\sin\)\(\cos\) 虽然算法竞赛中鲜有运用,但还是记录一下。

使用欧拉公式可知:

\[e ^ {ix} = \cos(x) + i \sin(x) \]

简单变形可得:

\[e ^ {-ix} = \cos(x) - i \sin(x) \]

因此可以推导出 \(\cos(x)\)\(\cos(x)\) 关于 \(e ^ {ix}\)\(e ^ {-ix}\) 的反演:

\[\begin{aligned} \cos(x) &= \frac{e ^ {ix} + e ^ {-ix}}{2} \\ \sin(x) &= \frac{e ^ {ix} - e ^ {-ix}}{2i} \\ \end{aligned} \]

其中,\(i\) 在复数意义下相当于四次单位根,因此 \(i \equiv g ^ \frac{p - 1}{4} \pmod p\),其中 \(p\)\(\text{NTT}\) 使用的模数,形如 \(2 ^ m \times n + 1\)

使用多项式 \(\exp\) 即可求解上式。

一个简单的 \(\text{trick}\)\(e ^ {-ix}\) 可以由 \(e ^ {ix}\) 求逆来计算,减小常数。

多项式反三角函数

多项式 \(\arcsin\) 等反三角函数更是鲜有应用。

求解多项式反三角函数的方法是对原式求导再积分:

\[\begin{aligned} (\arcsin(f(x)))' &= \frac{f'(x)}{\sqrt{1 - f(x) ^ 2}} \\ \Rightarrow \arcsin(f(x)) &= \int (\arcsin(f(x)))' \mathrm{d}x \\ &= \int \frac{f'(x)}{\sqrt{1 - f(x) ^ 2}} \mathrm{d}x \end{aligned} \]

则原式得以求解。

求解其他反三角函数的方法与该方法类似,这里不再赘述。

多项式复合

泰勒级数

对于函数 \(f(x)\),如果它在一点 \(x = a\) 处有充分阶导数,我们可以使用一个级数来逼近它。

根据导数的定义,我们可以作如下的变换:

\[\begin{aligned} f'(a) &= \lim_{h \rightarrow 0} \frac{f(a + h) - f(a)}{h} \nonumber \\ \Rightarrow f(a + h) &\approx f(a) + f'(a)h \\ \end{aligned} \]

迭代上述过程,可得:

\[\begin{aligned} f(a + h) &= f(a) + f'(a)h + \frac{f''(a)}{2!}h ^ 2 + \frac{f'''(a)}{3!}h ^ 3 + \dots \\ &= \sum_{k = 0} ^ \infty \frac{f ^ {(k)}(a)}{k!} h ^ k \end{aligned} \]

\(h\) 代换为 \(x - a\),可得:

\[f(x) = \sum_{k = 0} ^ \infty \frac{f ^ {(k)} (a)}{k!} (x - a) ^ k \]

一般情况下,我们会对函数 \(f(x)\)\(x = 0\) 处展开,所得的泰勒级数也称 麦克劳林级数

常用结论:

\[\begin{aligned} e ^ x &= \sum_{k = 0} ^ \infty \frac{1}{k!} x ^ k & x \in \mathbb{R} \\ \frac{1}{1 - x} &= \sum_{k = 0} ^ \infty x ^ k & |x| < 1 \\ (1 + x) ^ \alpha &= \sum_{k = 0} ^ \infty \binom{\alpha}{k} x ^ k & |x| < 1, \alpha \in \mathbb{C} \end{aligned} \]

模板

多项式模板代码,汇总了部分常用的功能。

#include <bits/extc++.h>

#define inline __always_inline
const int MaxA = 1 << 21, MaxN = 1e5 + 5, mod = 998244353, G[2] = {3, (mod + 1) / 3};

inline int qpow(int x, int n)
{
	int ans = 1;
	for (; n; n >>= 1, x = 1l * x * x % mod)
		if (n & 1) ans = 1l * ans * x % mod;
	return ans;
}
int lim, l, w[2][MaxA], r[MaxA], inv[MaxN];			// NTT 数域内的多项式次数实际最高为 lim - 1 次。
void init_inv()
{
	inv[1] = 1;
	for (int i = 2; i < MaxN; i++) inv[i] = mod - 1l * mod / i * inv[mod % i] % mod;
}
void init_NTT(int n) 								// NTT 数域内的多项式次数期望最高为 n - 1 次。
{
	for (lim = 1, l = 0; lim < n; lim <<= 1, l++);
	for (int i = 0; i < lim; i++)
		r[i] = r[i >> 1] >> 1 | (i & 1) << l - 1;	// 预处理位逆序
	for (int h = 1; h < lim; h <<= 1)				// 预处理 2 ^ n 次单位根
		w[0][h] = qpow(G[0], (mod - 1) / (h << 1)),
		w[1][h] = qpow(G[1], (mod - 1) / (h << 1));
}
struct poly_t
{
	int a[MaxA];
	poly_t() { memset(a, 0, sizeof(a)); }
	inline auto &operator[](int p) { return a[p]; }
	inline auto &operator[](int p) const { return a[p]; }
	int operator()(int x) const
	{
		int ans = 0;
		for (int i = 0; i < lim; i--) ans = (1l * ans * x + a[i]) % mod;
		return ans;
	}
	poly_t &operator+=(const poly_t &f)
	{
		for (int i = 0; i < lim; i++) a[i] = (a[i] + f[i]) % mod;
		return *this;
	}
	poly_t &operator*=(int x)
	{
		for (int i = 0; i < lim; i++) a[i] = 1l * a[i] * x % mod;
		return *this;
	}
	poly_t &operator*=(const poly_t &f)
	{
		for (int i = 0; i < lim; i++) a[i] = 1l * a[i] * f[i] % mod;
		return *this;
	}
	poly_t &Diff(const poly_t &f, int n)
	{
		for (int i = 1; i < n; i++) a[i - 1] = 1l * f[i] * i % mod;
		a[n - 1] = 0; return *this;
	}
	poly_t &Int(const poly_t &f, int n)
	{
		for (int i = n - 1; i; i--) a[i] = 1l * f[i - 1] * inv[i] % mod;
		a[0] = 0; return *this;
	}
	poly_t &NTT(int t)
	{
		for (int i = 0; i < lim; i++)
			if (i < r[i]) std::swap(a[i], a[r[i]]);
		for (int h = 1; h < lim; h <<= 1)			// 枚举半步长
			for (int i = 0; i < lim; i += h << 1)	// 枚举起始位置 i
				for (int j = 0, s = 1; j < h; j++, s = 1l * s * w[t][h] % mod)		// s = w ^ j
				{
					int u = a[i | j], v = 1l * s * a[i | h | j] % mod;				// 使用位运算简化代码
					a[i | j] = (u + v) % mod, a[i | h | j] = (u - v + mod) % mod;	// 蝴蝶操作
				}
		if (t) for (int i = 0, inv = qpow(lim, mod - 2); i < lim; i++)				// 如果是 NTT 逆变换,不要忘记对系数 / lim
			a[i] = 1l * a[i] * inv % mod;
		return *this;
	}
	poly_t &Inv(const poly_t &f, int n)
	{
		assert(&f != this), memset(a, 0, sizeof(a));	// ! important
		poly_t g; a[0] = qpow(f[0], mod - 2);
		for (int m = 2; m < n << 1; m <<= 1)
		{
			memcpy(g.a, f.a, m << 2);
			init_NTT(m << 1), NTT(0), g.NTT(0);		// ! 必须扩大 NTT 数域以保证精度
			for (int i = 0; i < lim; i++)
				a[i] = (2 - 1l * g[i] * a[i] % mod + mod) * a[i] % mod;
			NTT(1);
			memset(a + m, 0, m << 2);				// 尽管最初扩大了一倍 NTT 数域,但实际可用的部分仍然只有 [0, m) 部分
		}
		return *this;
	}
	poly_t &ln(const poly_t &f, int n)
	{
		poly_t g, h; init_NTT(n << 1);
		return Int((g.Diff(f, n).NTT(0) *= h.Inv(f, n).NTT(0)).NTT(1), n);
	}
	poly_t &exp(const poly_t &f, int n)
	{
		assert(&f != this), memset(a, 0, sizeof(a));	// ! important
		poly_t g; a[0] = 1;
		for (int m = 2; m < n << 1; m <<= 1)
		{
			g.ln(*this, m)[0]--;					// 不要忘记对常数项加 1
			for (int i = 0; i < m; i++) g[i] = (f[i] - g[i] + mod) % mod;
			init_NTT(m << 1), (NTT(0) *= g.NTT(0)).NTT(1);
		}
		return *this;
	}
	poly_t &pow(const poly_t &f, int k, int n) { return exp(poly_t(ln(f, n) *= k), n); }
};
posted @ 2024-11-03 00:15  yiming564  阅读(254)  评论(0)    收藏  举报