拉格朗日插值
插值问题形如 :给定 \(n\) 个点的坐标 \((x, y)\),要求出一个经过所有点的最低次多项式。
一般插值
容易构造出 \(f(x)\) 即为下式 :
\[ f(x) = \sum _{i = 1} ^{n} y_i \prod _{j \not= i} \frac{x - x_j}{x_i - x_j}\tag{1.1.1} \]正确性显然。
容易证明多项式次数至少为 \(n - 1\) 才可以确保穿过 \(n\) 个点,故最优性得证。
时间复杂度 \(\Theta(n^{2})\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 2e3 + 10, mod = 998244353;
int n, k, x[N], y[N], ans;
int Pow (int a, int k) {
int res = 1;
for ( ; k; a = 1ll * a * a % mod, k >>= 1)
if(k & 1) res = 1ll * res * a % mod;
return res;
}
int main () {
scanf("%d%d", &n, &k);
rep(i, 1, n) scanf("%d%d", &x[i], &y[i]);
rep(i, 1, n) {
int S1 = 1, S2 = 1;
rep(j, 1, n) if (j != i) {
S1 = 1ll * S1 * (k - x[j]) % mod;
S2 = 1ll * S2 * (x[i] - x[j]) % mod;
}
ans = (ans + 1ll * y[i] * S1 % mod * Pow(S2, mod - 2)) % mod;
}
printf("%d\n", (ans + mod) % mod);
return 0;
}
点值连续的插值
当给定的点值连续时,式子可以改写为 :
\[ \begin{aligned} f(x) = \sum _{i = 1} ^{n} y_i \prod _{j \not= i} \frac{x - j}{i - j} = \sum _{i = 1} ^{n} y_i \frac{\prod \limits _{j \not= i}(x - j)}{\prod \limits_{j \not= i}(i - j)} \end{aligned} \]考虑计算分子,记 \(pre_{i} = \prod_{j = 1}^{i} (x - j), suf_{i} = \prod_{j = i}^{n} (x - j)\),显然 \(\prod_{j \not= i}(x - j) = pre_{i - 1}\times suf_{i + 1}\)。
考虑计算分母,记 \(sym(x) = 1 - 2[x \equiv 1 \pmod 2]\),显然 \(\prod_{j \not= i}(i - j) = (i - 1)!\times sym(n - i)(n - i)!\)。
全部带入可将式 \((1.2.1)\) 改写为 :
\[ f(x) = \sum _{i = 1} ^n y_i \frac{pre_{i - 1}\times suf_{i + 1}}{(i - 1)!\times sym(n - i)(n - i)!} \tag{1.2.2} \]时间复杂度 \(\Theta(n)\)。
int F (int n, int k) {
fac[0] = 1;
rep(i, 1, k + 2) fac[i] = fac[i - 1] * i % mod;
inv[k + 2] = Pow(fac[k + 2], mod - 2);
dep(i, k + 1, 0) inv[i] = inv[i + 1] * (i + 1) % mod;
pre[0] = suf[k + 3] = 1;
rep(i, 1, k + 2) pre[i] = pre[i - 1] * (n - i) % mod;
dep(i, k + 2, 1) suf[i] = suf[i + 1] * (n - i) % mod;
rep(i, 1, k + 2) y[i] = (y[i - 1] + Pow(i, k)) % mod;
int res = 0;
rep(i, 1, k + 2) res = (res + y[i] * pre[i - 1] % mod * suf[i + 1] % mod * inv[i - 1] % mod * inv[k + 2 - i] * (((k + 2 - i) & 1) ? -1 : 1)) % mod;
return (res + mod) % mod;
}
重心拉格朗日插值法
考虑一般插值的优化。
观察式 \((1.1.1)\),设 :
\[ G = \prod _{i = 1} ^n (x - x_i) \tag{1.3.1} \]\[ T_i = \frac{y_i}{\prod \limits_{j \not= i} (x_i - x_j)} \tag{1.3.2} \]将式 \((1.3.1)\) 和 \((1.3.2)\) 带入式 \((1.1.1)\) 可得 :
\[ f(x) = G \sum _{i = 1} ^{n} \frac{T_i}{x - x_i} \tag{1.3.3} \]如此一来,新加入一个点就只需要计算它的 \(T\) 值了,时间复杂度为 \(\Theta(n)\)。
还原插值多项式的系数
考虑对式 \((1.1.1)\) 作变形 :
\[ f(x) = \sum _{i = 1} ^{n} \frac{y_i}{\prod \limits_{j \not= i}(x_i - x_j)} \prod _{j \not= i} (x - x_j)\tag{1.4.1} \]可以 \(\Theta(n^{2})\) 预处理 \(\prod_{i = 1}^{n} (x - x_{i})\),然后在需要求 \(\prod_{j \not= i} (x - x_{j})\) 时将预处理出的多项式除以 \((x - x_{i})\) ,可以做到单次 \(\Theta(n)\) 的时间复杂度。
还原插值多项式系数的时间复杂度为 \(\Theta(n^{2})\)。
自然数幂前缀和
有定理:不大于 \(n\) 的自然数 \(k\) 次幂和是一个关于 \(n\) 的 \(k + 1\) 次多项式。
\(\rm Proof :\)
设 \(f_{k}(n) = \sum \limits_{i = 1}^{n} i^{k}\)。
当 \(k = 1\) 时,\(f_{k}(n) = \sum \limits_{i = 1}^{n} i = \frac{1}{2}n^{2} + \frac{1}{2} n\),是一个 \(k + 1\) 次多项式。
假设 \(f_{k - 1}(n)\) 是一个关于 \(n\) 的 \(k\) 次多项式。
即 \(f_{k - 1}(n) = a_{0} + a_{1} n + a_{2} n^{2} + \cdots + a_{k} n^{k}\)。
\[ \begin{aligned} f_k(n) &= \sum _{i = 1} ^n i\times i ^ {k - 1}\\ &= \sum _{i = 0} ^{n - 1} f_{k - 1}(n) - f_{k - 1}(i)\\ &= nf_{k - 1}(n) - \sum _{i = 1} ^{n - 1} f_{k - 1}(i)\\ &= a_0 n + a_1 n ^ 2 + \cdots + a_k n ^ {k + 1} - \sum _{i = 1} ^{n - 1} f_{k - 1}(i) \end{aligned} \]容易发现,由于 \(f_{k - 1}(i)\) 中次数最高的项为 \(k\) 次项,并且 \(f_{k}(n)\) 中含有关于 \(n\) 的 \(k + 1\) 次项,所以 \(f_{k}(n)\) 是关于 \(n\) 的 \(k + 1\) 次多项式。
归纳可得 :\(\forall k\geqslant 1,\ f_{k}(n)\) 是关于 \(n\) 的 \(k\) 次多项式。
于是可以使用点值连续的拉格朗日插值计算 \(\sum \limits_{i = 1}^{n} i^{k}\),时间复杂度 \(\Theta(k)\)。
快速傅里叶变换
快速傅里叶变换 \(\rm(Fast\ Fourier\ Transformation,FFT)\) 被用来在 \(\Theta(n\log n)\) 的时间复杂度下求两个多项式的乘积(和卷积)。
若称 \(h(x) = f(x) \times g(x)\),则应满足关系式 :
\[ h(k) = \sum _{i + j = k} f(i) \times g(j) \tag{2.0.1} \]单位根
众所周知,有欧拉公式 :
\[ e^{i\theta} = \cos \theta + i\sin \theta \tag{2.1.1} \]根据高中课本上所讲的,我们知道任意一个复数可以被表示成 \(r(\cos \theta + i\sin \theta)\) 的形式,根据式 \((2.1.1)\),可以发现其等价于 \(r \cdot e^{i\theta}\),所以两个复数相乘,模长 \((r)\) 相乘,幅角 \((\theta)\) 相加。
考虑方程 \(x^{n} = 1\),容易发现它在复数域内有 \(n\) 个解,解集为 \(\{e^{i\theta} \mid \theta = \frac{2k\pi}{n},k = 0,1,2, \cdots, n - 1 \}\),将其简记为 \(\omega_{n}^{0},\omega_{n}^{1},\cdots,\omega_{n}^{n - 1}\),进一步观察可以发现,它们是复平面中单位圆上的 \(n\) 等分点。
之所以会在 \(\rm FFT\) 中用到单位根,主要是因为它具有以下性质 :
折半性质 :
\[ \omega_{2n} ^ {2k} = \omega _n ^k \tag{2.1.2} \]\(\rm Proof :\) 显然有 \(\omega_{n}^{k} = (\omega_{n}^{1})^{k}\) 和 \(\omega_{n}^{1} = \omega_{2n}^{2}\)。
对称性质 :
\[ \omega _{2n} ^ {k + n} = -\omega _{2n} ^{k} \tag{2.1.3} \]\(\rm Proof :\) 这里定义 \(-z\) 为 \(z\) 关于原点的对称点,显然有 \(\omega_{2n}^{n} = -1\),\(\omega_{2n}^{k + n} = \omega_{2n}^{k}\times \omega_{2n}^{n} = -\omega_{2n}^{k}\)。
求和性质 :
\[ \sum _{i = 0} ^{n - 1} \left(\omega_{n} ^k\right) ^i = n[k = 0] \tag{2.1.4} \]\(\rm Proof :\)
首先使用等比数列求和公式 :
\[ \sum _{i = 0} ^{n - 1} \left(\omega_{n} ^k\right) ^i = \frac{1 - \left(\omega_{n} ^k\right)^n}{1 - \omega_n^k} \tag{2.1.5} \]考虑分类讨论 :
- 当 \(k = 0\) 时,原式显然等于 \(n\)。
- 当 \(k \not = 0\) 时,\(1 - \omega_{n}^{k} \not = 0\),\(1 - \left(\omega_{n}^{k}\right)^{n} = 0\),故原式等于 \(0\)。
离散傅里叶变换
离散傅里叶变换 \(\rm(Discrete\ Fourier\ Transform,DFT)\) 被用于求出多项式的 \(n\) 个点值,其中 \(n\) 为多项式的项数,\(\rm DFT\) 是 \(\rm FFT\) 的前半部分。
对于一个多项式 \(F(x) = a_{0} + a_{1} x + a_{2} x^{2} + \cdots + a_{n - 1} x^{n - 1}\),不妨设 :
\[ \begin{aligned} F_1(x) = a_0 + a_2x + a_4x^2 + \cdots + a_{n - 2}x^{\frac{n}{2} - 1}\\ F_2(x) = a_1 + a_3x + a_5x^2 + \cdots + a_{n - 1}x^{\frac{n}{2} - 1} \end{aligned} \]于是有 \(F(x) = F_{1}(x^{2}) + xF_{2}(x^{2})\)。
将 \(\omega_{n}^{k}\) 带入可得 :
\[ F(\omega_n^k) = F_1(\omega_n^{2k}) + \omega_n^k F_2(\omega_n^{2k}) \tag{2.2.1} \]根据式 \((2.1.2)\) 可得 :
\[ F(\omega_n^k) = F_1(\omega_{n/2}^{k}) + \omega_n^k F_2(\omega_{n/2}^{k}) \tag{2.2.2} \]考虑分类讨论 :
对于 \(k \lt \frac{n}{2} \),只需要直接递归处理 \(F_{1}\) 和 \(F_{2}\) 即可。 对于 \(k \geqslant \frac{n}{2}\),我们不妨它为 \(k + \frac{n}{2}\ (k \lt \frac{n}{2})\),于是有 :
\[ F(\omega_n^{k + n/2}) = F_1(\omega_n^{2k + n}) + \omega_n^{k + n/2} F_2(\omega_n^{2k + n}) \tag{2.2.3} \]根据式 \((2.1.2)\) 和 \((2.1.3)\) 可得 :
\[ F(\omega _ n ^ k) = F_1(\omega_{n / 2} ^ k) - \omega _ n ^ k F_2(\omega_{n/2}^k) \tag{2.2.4} \]观察发现,式 \((2.2.2)\) 和 \((2.2.4)\) 之间仅相差一个符号,这意味着我们只需要递归求出 \(F_{1}\) 和 \(F_{2}\) 的 \(\frac{n}{2}\) 个点值就可以在 \(\Theta(n)\) 的时间复杂度下求出 \(F\) 的 \(n\) 个点值。
因为最多递归 \(\log n\) 次,每一层的总时间复杂度为 \(\Theta(n)\),所以 \(\rm DFT\) 过程的时间复杂度为 \(\Theta(n\log n)\)。
离散傅里叶逆变换
离散傅里叶逆变换 \(\rm(Inverse\ Discrete\ Fourier\ Transform,IDFT)\) 被用于将多项式的点值表示还原成系数表示,是 \(\rm FFT\) 的后半部分。
在 \(\rm DFT\) 过程中我们已经将待乘函数 \(f(x)\) 和 \(g(x)\) 的点值表示分别求了出来,易知它们卷积的点值序列为它们点值序列各处分别相乘的结果。
设 \(\{G_{n}\}\) 为 \(F(x)\) 在 \(\rm DFT\) 过程中求出的点值序列,\(\{F_{n}\}\) 是 \(F(x)\) 的系数序列,则根据定义有 :
\[ G_k = \sum _{i = 0} ^{n - 1} \left(\omega_{n}^{k}\right) ^ i F_i \tag{2.3.1} \]此处有反演结论 :
\[ nF_k = \sum_{i = 0} ^{n - 1} \left(\omega_{n}^{-k}\right) ^ i G_i \tag{2.3.2} \]\(\rm Proof :\)
\[ \begin{aligned} nF_k &= \sum_{i = 0} ^ {n - 1} \sum _{j = 0} ^ {n - 1} \left(\omega_{n} ^{-k}\right) ^ i \left(\omega_{n} ^{i}\right) ^ j F_j\\ &= \sum_{i = 0} ^ {n - 1} \sum _{j = 0} ^ {n - 1} \omega_{n} ^{i(j - k)} F_j \end{aligned} \]将式 \((2.1.4)\) 带入 \((2.3.3)\) 可得 :
\[ \begin{aligned} nF_k &= \sum _{j = 0} ^ {n - 1} \sum_{i = 0} ^ {n - 1} \left(\omega_{n} ^{j - k}\right)^i F_j\\ &= \sum_{j = 0} ^ {n - 1} n[j = k] F_j\\ &= nF_k \end{aligned} \]故利用式 \((2.3.2)\) 将 \(\{G_{n}\}\) 带入 \(\rm DFT\) 过程,以 \(\omega_{n}^{-1}\) 作为一次单位根得出的点值即为 \(F(x)\) 的系数序列。
\(\rm IDFT\) 过程的时间复杂度为 \(\Theta(n\log n)\)。
至此,我们已经推导出了一个时间复杂度为 \(\Theta(n\log n)\),可以递归实现的 \(\rm FFT\) 算法,需要注意的是,由于递归时需要两边长度相等,所以 \(F(x)\) 的长度必须是 \(2\) 的幂,少了的部分通过在高次项以 \(0\) 作为系数来补齐。
位逆序置换
位逆序置换 \(\rm(Bit-Reversal\ Permutation)\) 是 \(\rm FFT\) 中一个较大的优化,在国内又称蝴蝶变换。
它的优化主要在于将原本的递归实现变成了迭代实现。
在递归实现中,我们每一次都会把整个多项式的奇数次项和偶数次项系数分开,一直分到只剩下一个系数。但是我们也可以先把这些系数在原数组中拆分,然后再倍增地去合并这些算出来的值。
以 \(8\) 项多项式为例,模拟拆分的过程 :
- 初始序列为 : \(\left\{x_{0}, x_{1}, x_{2}, x_{3}, x_{4}, x_{5}, x_{6}, x_{7}\right\}\)。
- 一次二分之后 : \(\left\{x_{0}, x_{2}, x_{4}, x_{6}\right\},\left\{x_{1}, x_{3}, x_{5}, x_{7}\right\}\)。
- 两次二分之后 : \(\left\{x_{0}, x_{4}\right\}, \left\{x_{2}, x_{6}\right\},\left\{x_{1}, x_{5}\right\},\left\{x_{3}, x_{7}\right\}\)。
- 三次二分之后 : \(\left\{x_{0}\right\}, \left\{x_{4}\right\}, \left\{x_{2}\right\}, \left\{x_{6}\right\}, \left\{x_{1}\right\}, \left\{x_{5}\right\}, \left\{x_{3}\right\}, \left\{x_{7}\right\}\)。
可以发现,只要把最后一层每个位置对应的坐标求出来,然后每次迭代时插空合并相邻两项即可。
记 \(R(x)\) 表示 \(x\) 在全部分治完后去到的位置,观察发现 \(R(x)\) 是 \(x\) 在二进制意义下翻转后的值。
首先有 \(R(0) = 0\),剩下的考虑从小到大求解,显然有 :
\[ R(x) = \left\lfloor\frac{R\left(\left\lfloor\frac{x}{2}\right\rfloor\right)}{2}\right\rfloor+(x \bmod 2) \times 2^{k - 1} \tag{2.4.1} \]求解 \(R(x)\) 并将每个数换到应该去的位置时间复杂度为 \(\Theta(n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e6;
const double Pi = acos(-1.0);
struct Complex { double x, y; } F[N], G[N];
Complex operator + (Complex a, Complex b) { return (Complex){a.x + b.x, a.y + b.y}; }
Complex operator - (Complex a, Complex b) { return (Complex){a.x - b.x, a.y - b.y}; }
Complex operator * (Complex a, Complex b) { return (Complex){a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }
int n, m, len, ln, R[N];
void FFT (Complex *F, int type) {
rep(i, 0, len - 1) if (i < R[i]) swap(F[i], F[R[i]]);
for (int k = 1; k < len; k <<= 1) {
Complex eps = (Complex){cos(Pi / k), type * sin(Pi / k)};
for (int i = 0; i < len; i += (k << 1)) {
Complex w = (Complex){1, 0};
for (int j = i; j < i + k; j++, w = w * eps) {
Complex tmp1 = F[j], tmp2 = w * F[j + k];
F[j] = tmp1 + tmp2, F[j + k] = tmp1 - tmp2;
}
}
}
}
int main () {
scanf("%d%d", &n, &m);
rep(i, 0, n) scanf("%lf", &F[i].x);
rep(i, 0, m) scanf("%lf", &G[i].x);
for (len = 1, ln = 0; len <= n + m; len <<= 1, ln++);
rep(i, 0, len - 1) R[i] = (R[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
FFT(F, 1), FFT(G, 1);
rep(i, 0, len - 1) F[i] = F[i] * G[i];
FFT(F, -1);
rep(i, 0, n + m) printf("%d ", (int)round(F[i].x / len));
return 0;
}
快速数论变换
由于 \(\rm FFT\) 对单位根的依赖,所以该算法必须使用浮点数保存,进而引发精度问题。糟糕的是,数学家已经证明了在复数域内仅有单位根满足所需性质。
值得庆幸的是,大多数计数问题是在模意义下完成的,于是我们希望为 \(\rm FFT\) 找一个模意义下的替代品,最终方案为快速数论变换 \(\rm(Number\ Theoretic\ Transform,NTT)\)。
原根
原根的定义如下 :
如果有 \(a^{n} \equiv 1 \pmod p\),则称满足此条件最小的 \(n\) 为 \(a\) 在模 \(p\) 意义下的阶。若一个数 \(g\) 在模 \(p\) 意义下的原根为 \(\varphi(p)\),那么称 \(g\) 为 \(p\) 的原根。
我们尝试用原根代替单位根进行运算,所以需要先证明以下几条性质 :
不重性质:
\[ \forall 0\leqslant i \lt j \lt \varphi (p),g^i \not \equiv g^j \pmod p \tag{3.1.1} \]\(\rm Proof :\) 若存在 \(0\leqslant i \lt j \lt \varphi (p)\) 满足 \(g^{i} \equiv g^{j} \pmod p\),那么 \(0\leqslant j - i \lt \varphi (p)\) 且 \(g^{j - i} \equiv 1 \pmod p\),与原根的定义矛盾,故原命题得证。
折半性质:
定义 \(g_{n}^{1} = g^{\frac{p - 1}{n}},g_{n}^{k} = (g_{n}^{1})^{k}\) 则 :
\[ g_{2n}^{2k} \equiv g_n^k \pmod p \tag{3.1.2} \]\(\rm Proof :\) 由式 \((3.1.1)\) 可得 \(g_{n}^{0},g_{n}^{1},g_{n}^{2},\cdots,g_{n}^{n - 1}\) 互不相同。将 \(g_{n}^{k}\) 的定义带入易证式 \((3.1.2)\)。
对称性质:
\[ g_{2n}^{k + n} \equiv -g_{2n}^k \pmod p \tag{3.1.3} \]\(\rm Proof :\)
参照式 \((2.1.3)\) 的证明方法,我们只需要证明 \(g_{2n}^{n} \equiv -1 \pmod p\) 即可。
\[ (g_{2n}^n) ^ 2 \equiv (g^{\frac{p - 1}{2}}) ^ 2 \equiv g^{p - 1} \equiv 1 \pmod p \tag{3.1.4} \]所以 \(g_{2n}^{n} \equiv 1\ or-1 \pmod p\)。
根据式 \((3.1.1)\),我们知道 \(g_{2n}^{n} \not \equiv g^{p - 1} \pmod p\),又因为 \(g^{p - 1} \equiv 1 \pmod p\),故 \(g_{2n}^{n} \equiv -1 \pmod p\)。
求和性质:
\[ \sum _{i = 0} ^{n - 1} \left(g_{n} ^k\right) ^i \equiv n[k = 0] \pmod p \tag{3.1.5} \]\(\rm Proof :\) 参照式 \((2.1.4)\) 的证明。
有了以上 \(4\) 条性质,我们就可以直接将原根带入 \(\rm DFT\) 运算了。
另外,由于 \(\rm DFT\) 运算中的多项式长度 \(n\) 均为 \(2\) 的幂,并且我们取的模数 \(p\) 需要满足 \(p - 1\) 能整除足够大的 \(n\),所以 \(p - 1\) 要包含一个较大的因子是 \(2\) 的幂。
比较常用的 \(\rm NTT\) 模数是 \(998244353 = 119 \times 2^{23} + 1\),它的最小原根是 \(3\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e6, mod = 998244353;
int n, m, len, ln, F[N], G[N], rev[N];
int Pow (int a, int k) {
int res = 1;
for ( ; k; a = 1ll * a * a % mod, k >>= 1)
if(k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int *F, bool type) {
rep(i, 0, len) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
int main () {
scanf("%d%d", &n, &m);
rep(i, 0, n) scanf("%d", &F[i]);
rep(i, 0, m) scanf("%d", &G[i]);
for (len = 1, ln = 0; len <= n + m; len <<= 1, ln++);
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(F, true), NTT(G, true);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * G[i] % mod;
NTT(F, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, n + m) printf("%lld ", 1ll * F[i] * Inv % mod);
return 0;
}
Chirp Z 变换
\(\rm Chirp\ Z-Transform\) 又称 \(\rm Bluestein\) 算法,与 \(\rm DFT\) 类似,它被用于在 \(\Theta[(n + m) \log n]\) 的时间复杂度内求解如下问题 :
给定 \(n\) 次多项式 \(F(x)\),求出 \(F(1),F(c),F(c^{2}),\cdots,F(c^{m})\),\(c \in \mathbb{C}\)。
考虑组合恒等式 :
\[ ik = \dbinom{i + k}{2} - \dbinom{i}{2} - \dbinom{k}{2} \tag{3.2.1} \]\(\rm Proof :\)
\[ \begin{aligned} ik &= \frac{2ik}{2}\\ &= \frac{(i ^ 2 + 2ik + k ^ 2 - i - k) - (i ^ 2 - i) - (k ^ 2 - k)}{2}\\ &= \dbinom{i + k}{2} - \dbinom{i}{2} - \dbinom{k}{2} \end{aligned} \]于是有 :
\[ \begin{aligned} F(c ^ k) &= \sum _{i = 0} ^ {n - 1} a_i c ^ {ik}\\ &= \sum _{i = 0} ^ {n - 1} a_i c ^ {\binom{i + k}{2} - \binom{i}{2} - \binom{k}{2}}\\ &= c ^ {-\binom{k}{2}} \sum _{i = 0} ^{n - 1} \left[a_i c ^ {-\binom{i}{2}} \right] \left[c ^ {\binom{i + k}{2}} \right] \end{aligned} \]设 \(f(x) = c^{\binom{x}{2}}, g(x) = a_{xc}^{-\binom{x}{2}}\),则有 :
\[ \frac{F(c ^ k)}{c ^ {-\binom{k}{2}}} = \sum _ {i - j = k} f(i) \times g(j) \tag{3.2.4} \]可以使用减法卷积来计算,时间复杂度 \(\Theta[(n + m) \log n]\)。
具体来说,计算减法卷积时一般将 \(g(x)\) 翻转,即 \(g'(i) = g(m - i)\),于是有 :
\[ \begin{aligned} \frac{F(c ^ k)}{c ^ {-\binom{k}{2}}} &= \sum _ {i - j = k} f(i) \times g(j)\\ &= \sum _ {i + j = m + k} f(i) \times g'(j) \end{aligned} \]计算式 \((3.2.5)\) 时可以先对 \(f(x)\) 和 \(g(x)\) 求和卷积,称得到的多项式为 \(F(x)\)。
假设 \(f(x)\) 的次数为 \(n\),\(g(x)\) 的次数为 \(m\),那么 \(F(x)\) 有 \(n + m + 1\) 项。可以发现,现在 \(F(x)\) 的第 \(k\) 个位置实际上为答案的第 \(m + k\) 项,所以要将多项式向左平移 \(m\) 位,即 \(F'(k) = F(k + m)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e6, mod = 998244353;
int n, m, c, len, ln, inv, tmp, a[N], p[N], R[N], F[N], G[N];
int Pow (int a, int k) {
int res = 1;
for ( ; k; a = 1ll * a * a % mod, k >>= 1)
if(k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int *F, bool type) {
rep(i, 0, len - 1) if (i < R[i]) swap(F[i], F[R[i]]);
for (int k = 1; k < len; k <<= 1) {
int E = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, G = 1; j < i + k; j++, G = 1ll * G * E % mod) {
int tmp1 = F[j], tmp2 = 1ll * G * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
int main () {
scanf("%d%d%d", &n, &c, &m), n--, m--;
rep(i, 0, n) scanf("%d", &a[i]);
inv = Pow(c, mod - 2);
for (len = 1, ln = 0; len <= n + m; len <<= 1, ln++);
rep(i, 0, len - 1) R[i] = (R[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
F[0] = F[1] = tmp = 1;
rep(i, 2, n + m) tmp = 1ll * tmp * c % mod, F[i] = 1ll * F[i - 1] * tmp % mod;
p[0] = p[1] = tmp = 1;
rep(i, 2, max(n, m)) tmp = 1ll * tmp * inv % mod, p[i] = 1ll * p[i - 1] * tmp % mod;
rep(i, 0, n) G[i] = 1ll * a[i] * p[i] % mod;
reverse(G, G + n + 1);
NTT(F, true), NTT(G, true);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * G[i] % mod;
NTT(F, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * Inv % mod;
rep(i, 0, m) printf("%d ", 1ll * F[n + i] * p[i] % mod);
return 0;
}
分治多项式乘法
有些时候我们知道 \(G(x)\) 的所有项,需要计算 \(F(x)\),并且它们满足关系 :
\[ F_k = \sum _{i = 0} ^ {k - 1} F_iG_{k - i} \tag{3.3.1} \]这时候可以考虑采用 \(\rm CDQ\) 分治的思想,先递归计算左边半部分,再计算左边对右边的贡献。可以发现贡献是卷积的形式,于是可以用 \(\rm FFT/NTT\) 以 \(\Theta(n \log n)\) 的时间复杂度计算。
分治多项式乘法的时间复杂度为 \(T(n) = 2T(\frac{n}{2}) + \Theta(n \log n) = \Theta(n \log^{2} n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e5, mod = 998244353;
int n, F[N], G[N], rev[N], Ft[N], Gt[N];
int Pow (int a, int k) {
int res = 1;
for (; k; a = 1ll * a * a % mod, k >>= 1)
if (k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int len, int *F, bool type) {
rep(i, 0, len - 1) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
void cdq (int l, int r) {
if (r - l + 1 <= 30) {
rep(i, l + 1, r) rep(j, l, i - 1)
F[i] = (F[i] + 1ll * F[j] * G[i - j]) % mod;
return;
}
int mid = l + r >> 1;
cdq(l, mid);
int len = 1, ln = 0;
for (; len <= r + mid - 2 * l; len <<= 1, ln++);
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
rep(i, 0, mid - l) Ft[i] = F[i + l];
rep(i, mid - l + 1, len - 1) Ft[i] = 0;
rep(i, 0, r - l) Gt[i] = G[i];
NTT(len, Ft, true), NTT(len, Gt, true);
rep(i, 0, len - 1) Ft[i] = 1ll * Ft[i] * Gt[i] % mod;
NTT(len, Ft, false);
int Inv = Pow(len, mod - 2);
rep(i, mid - l + 1, r - l) F[i + l] = (F[i + l] + 1ll * Ft[i] * Inv) % mod;
cdq(mid + 1, r);
}
int main () {
scanf("%d", &n), n--;
rep(i, 1, n) scanf("%d", &G[i]);
F[0] = 1, cdq(0, n);
rep(i, 0, n) printf("%d ", F[i]);
return 0;
}
快速沃尔什变换
快速沃尔什变换 \(\rm(Fast\ Walsh-Hadamard\ Transform,FWT)\) 被用来在 \(\Theta(n\log n)\) 的时间复杂度下求两个多项式的位运算卷积。
若称 \(h(x) = f(x) \oplus g(x)\),其中 \(\oplus\) 是 \(\rm or,and\) 或 \(\rm xor\),则应满足关系式 :
\[ h(k) = \sum _{i \oplus j = k} f(i) \times g(j) \tag{4.0.1} \]构造 FWT 函数
在 \(\rm FFT\) 中,我们通过将多项式转换成点值表示并进行点积的方法避开了卷积,于是我们自然也希望将位运算卷积通过某种方式转换成点积运算。
设 \(F' = FWT(F)\) 表示幂级数 \(F\) 经过 \(\rm FWT\) 变换后得到的幂级数。
容易发现,我们构造的 \(\rm FWT\) 变换需要满足 :
\[ A \times B = C \iff FWT(A) \cdot FWT(B) = FWT(C) \tag{4.1.1} \]由于 \(\rm DFT\) 是线性变换,我们自然希望 \(\rm FWT\) 也是线性变换。
因此,考虑设 \(k_{i, j}\) 表示 \(F_{j}\) 对 \(F'_{i}\) 的贡献系数,故有 :
\[ F'_i = \sum _{j = 0} ^ {n - 1} k_{i, j} F_j \tag{4.1.2} \]根据 \(FWT(A) \cdot FWT(B) = FWT(C)\) 可以得到 :
\[ A'_i \times B'_i = C'_i \tag{4.1.3} \]\[ \left(\sum _{j = 0} ^ {n - 1} k_{i, j} A_j\right)\left(\sum _{p = 0} ^ {n - 1} k_{i, p} B_p\right) = \sum_{j = 0} ^ {n - 1} k_{i, j} C_j \tag{4.1.4} \]\[ \sum _{j = 0} ^ {n - 1}\sum _{p = 0} ^ {n - 1} k_{i, j} k_{i, p} A_j B_p = \sum_{j = 0} ^ {n - 1} k_{i, j} C_j \tag{4.1.5} \]根据 \(A \times B = C\) 可以得到 :
\[ C_p = \sum _{i \oplus j = p} A_i\times B_j \tag{4.1.6} \]\[ \sum_{p = 0} ^ {n - 1} k_{i, p} C_p = \sum_{p = 0} ^ {n - 1} k_{i, p}\sum _{t \oplus j = p} A_t B_j \tag{4.1.7} \]\[ \begin{aligned} \sum _{j = 0} ^ {n - 1}\sum _{p = 0} ^ {n - 1} k_{i, j} k_{i, p} A_j B_p &= \sum_{p = 0} ^ {n - 1} k_{i, p}\sum _{t \oplus j = p} A_t B_j \\ &= \sum _{p = 0} ^{n - 1} \sum _{t \oplus j = p} k_{i, t \oplus j} A_t B_j\\ &= \sum _{t = 0} ^ {n - 1} \sum_{j = 0} ^ {n - 1} k_{i, t \oplus j} A_t B_j \end{aligned} \]对比式 \((4.1.8)\) 中两边系数可以发现只需要满足 :
\[ k_{i, j}k_{i, p} = k_{i, j \oplus p} \tag{4.1.9} \]假设我们已经求出了 \(k_{0, 0},k_{0,1},k_{1,0},k_{1,1}\),那么可以构造 \(k_{i, j} = \prod_{t} k_{i_{t},j_{t}}\),其中 \(a_{t}\) 表示 \(a\) 在二进制下的第 \(t\) 位。这种构造方式导出的变换系数 \(k\) 推导 \(k_{i, j}k_{i, p} = k_{i, j \oplus p}\) 的充分性容易证明。
求解 FWT 变换
现在我们需要快速求解下式 :
\[ F'_i = \sum _{j = 0} ^{n - 1} k_{i, j} F_j \tag{4.2.1} \]我们考虑按位折半计算,下面设 \(\hat{x}\) 表示 \(x\) 去除二进制首位以后的值。
\[ \begin{aligned} F'_i &= \sum _{j = 0} ^{n / 2 - 1} k_{i, j} F_j + \sum _{j = n / 2} ^{n - 1} k_{i, j} F_j \\ &= \sum _{j = 0} ^{n / 2 - 1} k_{i_0, j_0}k_{\hat{i}, \hat{j}} F_j + \sum _{j = n / 2} ^{n - 1} k_{i_0, j_0}k_{\hat{i}, \hat{j}} F_j \\ &= k_{i_0, 0}\sum _{j = 0} ^{n / 2 - 1} k_{\hat{i}, \hat{j}} F_j + k_{i_0, 1}\sum _{j = n / 2} ^{n - 1} k_{\hat{i}, \hat{j}} F_j \end{aligned} \]设 \(F^{0}\) 表示幂级数 \(F'\) 下标的二进制首位为 \(0\) 的部分,相似地定义 \(F^{1}\)。
- 若 \(i_{0} = 0\),则有 \(F'_{i} = k_{0, 0} F^{0}_{i} + k_{0, 1} F^{1}_{i}\)。
- 若 \(i_{0} = 1\),则有 \(F'_{i + n/2} = k_{1, 0} F^{0}_{i} + k_{1, 1} F^{1}_{i}\)。
在该算法中,每次迭代会将长度减半,合并一层的时间复杂度为 \(\Theta(n)\),所以总时间复杂度为 \(\Theta(n\log n)\)。
此外,逆变换 \(\rm(IFWT)\) 时对变换矩阵 \(k\) 求个逆即可。
构造位矩阵
针对不同的位运算,我们需要构造不同的位矩阵 \(k\) 以拟合 \(\rm FWT\) 变换。
或卷积
首先我们知道 :
\[ k_{p, i}\times k_{p, j} = k_{p, i | j} \tag{4.3.1} \]首先可以观察到 \(k_{p, i} \times k_{p, i} = k_{p, i}\),故 \(k_{p, i} \in \{0, 1\}\)。
然后可以发现 \(k_{p, 0} \times k_{p, 1} = k_{p, 1}\),所以 \(k_{p, 0} = 1\)。 又因为矩阵如果一列都是 \(1\),那么它没有逆,故 \(k\) 仅可能为以下两种之一 :
\[ \begin{bmatrix} 1&0\\ 1&1 \end{bmatrix}~or~\begin{bmatrix} 1&1\\ 1&0 \end{bmatrix} \tag{4.3.2} \]由于右边那种等价于高维前缀和,所以我选择右边那种(事实上两种都可以)。
\[ \begin{aligned} F'_i &= F^0_i\\ F'_{i + n/2} &= F^0_i + F^1_i \end{aligned} \]对于逆变换,将矩阵求个逆就可以得到 \(k^{-1} = \begin{bmatrix} 1&0\\ -1&1 \end{bmatrix}\)。
\[ \begin{aligned} IF_i &= IF^{0}_i\\ IF_{i + n/2} &= -IF^0_i + IF^1_i \end{aligned} \]与卷积
可以发现与卷积同或卷积几乎完全一样,此处不赘述,仅给出其位矩阵和逆位矩阵 :
\[ k = \begin{bmatrix} 1&1\\ 0&1 \end{bmatrix},k^{-1} = \begin{bmatrix} 1&-1\\ 0&1 \end{bmatrix} \tag{4.3.5} \]异或卷积
首先我们知道 :
\[ k_{p, i}\times k_{p, j} = k_{p, i~xor~j} \tag{4.3.6} \]因为 \(k_{0, 0} \times k_{x, y} = k_{x, y}\),所以 \(k_{0, 0} = 1\)。
因为 \(k_{1, 1} \times k_{1, 1} = k_{1, 0}\),此时若 \(k_{1, 1} = k_{1, 0} = 0\),则有一行为 \(0\),矩阵无逆,故 \(k_{1, 1},k_{1, 0} \not= 0\)。
因为 \(k_{1, 0} \times k_{1, 1} = k_{1, 1}\),又因为 \(k_{1, 1} \not = 0\),所以 \(k_{1, 0} = 1\)。
因为 \(k_{1, 1} \times k_{1, 1} = k_{1, 0} = 1\),所以 \(k_{1, 1} \in \{1, -1\}\)。
因为 \(k_{0,1} \times k_{0, 1} = k_{0, 0} = 1\),所以 \(k_{0, 1} \in \{1, -1\}\)。
因为 \(k_{0, 1}\) 和 \(k_{1, 1}\) 不能相等,否则行列式为 \(0\),矩阵无逆,所以 \(k\) 仅可能为以下两种之一 :
\[ \begin{bmatrix} 1&1\\ -1&1 \end{bmatrix}~or~\begin{bmatrix} 1&1\\ 1&-1 \end{bmatrix} \tag{4.3.7} \]我们采用第二种,可推知 :
\[ \begin{aligned} F'_i &= F^0_i + F^1_i\\ F'_{i + n/2} &= F^0_i - F^1_i \end{aligned} \]对 \(k\) 求逆可以得到 \(k^{-1} = \begin{bmatrix} \frac{1}{2}&\frac{1}{2}\\ \frac{1}{2}&-\frac{1}{2} \end{bmatrix}\)。
\[ \begin{aligned} IF_i &= \frac{IF^0_i + IF^1_i}{2}\\ IF_{i + n/2} &= \frac{IF^0_i - IF^1_i}{2} \end{aligned} \]#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 2e5, mod = 998244353, Inv = 499122177;
int n, len, a[N], b[N], F[N], G[N];
void OR (int *F, bool type) {
for (int k = 1; k < len; k <<= 1)
for (int i = 0; i < len; i += (k << 1))
for (int j = i; j < i + k; j++)
F[j + k] = (F[j + k] + (type ? F[j] : -F[j])) % mod;
}
void AND (int *F, bool type) {
for (int k = 1; k < len; k <<= 1)
for (int i = 0; i < len; i += (k << 1))
for (int j = i; j < i + k; j++) {
int tmp1 = F[j], tmp2 = F[j + k];
F[j] = ((type ? 0 : -tmp1) + tmp2) % mod;
F[j + k] = (tmp1 + (type ? tmp2 : 0)) % mod;
}
}
void XOR (int *F, bool type) {
for (int k = 1; k < len; k <<= 1)
for (int i = 0; i < len; i += (k << 1))
for (int j = i; j < i + k; j++) {
int tmp1 = F[j], tmp2 = F[j + k];
F[j] = (type ? 2ll : 1ll) * Inv * (tmp1 + tmp2) % mod;
F[j + k] = (type ? 2ll : 1ll) * Inv * (tmp1 - tmp2) % mod;
}
}
int main () {
scanf("%d", &n), len = 1 << n;
rep(i, 0, len - 1) scanf("%d", &a[i]);
rep(i, 0, len - 1) scanf("%d", &b[i]);
rep(i, 0, len - 1) F[i] = a[i], G[i] = b[i];
OR(F, true), OR(G, true);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * G[i] % mod;
OR(F, false);
rep(i, 0, len - 1) printf("%d ", F[i] < 0 ? F[i] + mod : F[i]); puts("");
rep(i, 0, len - 1) F[i] = a[i], G[i] = b[i];
AND(F, true), AND(G, true);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * G[i] % mod;
AND(F, false);
rep(i, 0, len - 1) printf("%d ", F[i] < 0 ? F[i] + mod : F[i]); puts("");
rep(i, 0, len - 1) F[i] = a[i], G[i] = b[i];
XOR(F, true), XOR(G, true);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * G[i] % mod;
XOR(F, false);
rep(i, 0, len - 1) printf("%d ", F[i] < 0 ? F[i] + mod : F[i]); puts("");
return 0;
}
多项式基本操作
多项式之间除了乘法,还可以做一些其它的基本操作,但它们都是基于多项式乘法的。
多项式求逆
若两个多项式 \(F(x)\) 和 \(G(x)\) 满足 \(F(x)G(x) = 1\),则称它们互为逆元,记作 \(G(x) = F^{-1}(x)\)。
求解多项式乘法逆元的时候考虑使用倍增的思想,不断扩大倍增上界。
当多项式的界为 \(\bmod\ x\) 时,直接对多项式的常数项求逆元即可。
设 \(G(x) = F^{-1}(x) \pmod {x^{n}}\),如果我们已经求出了 \(G'(x) = F^{-1}(x) \pmod {x^{n/2}}\),那么 :
\[ G(x) - G'(x) \equiv 0 \pmod {x^{n/2}} \tag{5.1.1} \]为了能配出 \(x^{n}\) 作为模数,考虑将式子两边同时平方,得到 :
\[ G^2(x) - 2G(x)G'(x) + G'^2(x) \equiv 0 \pmod {x ^ n} \tag{5.1.2} \]因为 \(F(x)G(x) = 1\),所以式子两边同时乘 \(F(x)\) 并整理可得 :
\[ G(x) = 2G'(x) - G'^2(x)F(x) \pmod {x ^ n} \tag{5.1.3} \]根据式 \((5.1.3)\) 可以倍增求出 \(F^{-1}(x)\),时间复杂度为 \(T(n) = T(\frac{n}{2}) + \Theta(n \log n) = \Theta(n\log n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e5, mod = 998244353;
int n, len, ln, F[N], R[N], G[N], g[N];
int Pow (int a, int k) {
int res = 1;
for ( ; k; a = 1ll * a * a % mod, k >>= 1)
if(k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int *F, bool type) {
rep(i, 0, len - 1) if (i < R[i]) swap(F[i], F[R[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += k << 1)
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
void INV (int n, int *F) {
G[0] = Pow(F[0], mod - 2);
for (len = 4, ln = 2; len <= (n << 2); len <<= 1, ln++) {
rep(i, len >> 1, len - 1) G[i] = g[i] = 0;
rep(i, 0, (len >> 1) - 1) g[i] = F[i];
rep(i, 0, len - 1) R[i] = (R[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(G, true), NTT(g, true);
rep(i, 0, len - 1) G[i] = 1ll * G[i] * (2 - 1ll * G[i] * g[i] % mod + mod) % mod;
NTT(G, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, (len >> 1) - 1) G[i] = 1ll * G[i] * Inv % mod;
rep(i, len >> 1, len - 1) G[i] = 0;
}
rep(i, 0, n) F[i] = G[i];
}
int main () {
scanf("%d", &n), n--;
rep(i, 0, n) scanf("%d", &F[i]);
INV(n, F);
rep(i, 0, n) printf("%d ", (F[i] + mod) % mod);
return 0;
}
多项式开方
若两个多项式 \(F(x)\) 和 \(G(x)\) 满足 \(G^{2}(x) = F(x)\),则称 \(F(x)\) 开根后为 \(G(x)\),记作 \(G(x) = F^{\frac{1}{2}}(x)\)。
与多项式求逆一样,多项式开根也利用了倍增法。
当多项式的界为 \(\bmod\ x\) 时,直接对常数项使用二次剩余即可。
设 \(G(x) = F^{\frac{1}{2}}(x) \pmod {x^{n}}\),如果我们已经求出了 \(G'(x) = F^{\frac{1}{2}}(x) \pmod {x^{n / 2}}\),那么 :
\[ G'^2 (x) - F(x) \equiv 0 \pmod {x ^ {n / 2}} \tag{5.2.1} \]对式 \((5.2.1)\) 两边同时平方并作简单变换,得到 :
\[ \left[G'^2(x) + F(x)\right] ^ 2 \equiv 4G'^2(x)F(x) \pmod {x ^ n} \tag{5.2.2} \]对式子两边同时除以 \(4G'^{2}(x)\),可以得到 :
\[ \left[\frac{G'^2(x) + F(x)}{2G'(x)}\right] ^ 2 \equiv F(x) \pmod {x ^ n} \tag{5.2.3} \]结合 \(G(x)\) 的定义,可以发现 :
\[ G(x) = \frac{1}{2}G'(x) + \frac{1}{2}G'^{-1}(x)F(x) \pmod {x ^ n} \tag{5.2.4} \]根据式 \((5.2.4)\) 可以倍增求出 \(F^{\frac{1}{2}}(x)\),时间复杂度为 \(T(n) = T(\frac{n}{2}) + \Theta(n \log n) = \Theta(n\log n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e5, mod = 998244353;
int n, F[N], rev[N], GI[N], FI[N], iG[N], GS[N], FS[N];
int Pow (int a, int k) {
int res = 1;
for ( ; k; a = 1ll * a * a % mod, k >>= 1)
if(k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int len, int *F, bool type) {
rep(i, 0, len - 1) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
void INV (int n, int *F) {
rep(i, 0, n << 2) FI[i] = GI[i] = 0;
GI[0] = Pow(F[0], mod - 2);
for (int len = 4, ln = 2; len <= n << 2; len <<= 1, ln++) {
rep(i, 0, (len >> 1) - 1) FI[i] = F[i];
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, GI, true), NTT(len, FI, true);
rep(i, 0, len - 1) GI[i] = 1ll * GI[i] * (2 - 1ll * GI[i] * FI[i] % mod + mod) % mod;
NTT(len, GI, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, (len >> 1) - 1) GI[i] = 1ll * GI[i] * Inv % mod;
rep(i, len >> 1, len - 1) GI[i] = 0;
}
rep(i, 0, n) iG[i] = GI[i];
}
void SQR (int n, int *F) {
rep(i, 0, n << 2) FS[i] = GS[i] = 0;
GS[0] = sqrt(F[0]);
for (int len = 4, ln = 2; len <= n << 2; len <<= 1, ln++) {
rep(i, 0, (len >> 1) - 1) FS[i] = F[i];
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
INV((len >> 1) - 1, GS);
NTT(len, GS, true), NTT(len, FS, true), NTT(len, iG, true);
rep(i, 0, len - 1) GS[i] = 499122177ll * (GS[i] + 1ll * FS[i] * iG[i] % mod) % mod;
NTT(len, GS, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, (len >> 1) - 1) GS[i] = 1ll * GS[i] * Inv % mod;
rep(i, len >> 1, len - 1) GS[i] = 0;
}
rep(i, 0, n) F[i] = GS[i];
}
int main () {
scanf("%d", &n), n--;
rep(i, 0, n) scanf("%d", &F[i]);
SQR(n, F);
rep(i, 0, n) printf("%d ", F[i]);
return 0;
}
多项式带余除法
给定多项式 \(F(x)\) 和 \(G(x)\),若满足关系式 \(Q(x)G(x) + R(x) = F(x)\),则称 \(Q(x)\) 和 \(R(x)\) 分别是 \(F(x)\) 除以 \(G(x)\) 的商和余数。
可以发现如果能消除 \(R(x)\) 的影响则可以直接使用多项式求逆。
考虑构造变换 :
\[ F' = x ^ {\deg F} F\left(\frac{1}{x}\right) \tag{5.3.1} \]可以发现,其本质就是翻转多项式系数。
设 \(n = \deg F,m = \deg G\),将多项式带余除法定义式中的 \(x\) 替换成 \(\frac{1}{x}\),并将两边同时乘 \(x^{n}\) 可得 :
\[ x ^ n F\left(\frac{1}{x}\right) = x ^ {n - m} Q\left(\frac{1}{x}\right) x ^ m G\left(\frac{1}{x}\right) + x ^ {n - m + 1} x ^ {m - 1} R\left(\frac{1}{x}\right) \tag{5.3.2} \]将式 \((5.3.1)\) 带入可得 :
\[ F'(x) = Q'(x) G'(x) + x ^ {n - m + 1} R'(x) \tag{5.3.3} \]注意到式 \((5.3.3)\) 中 \(R'(x)\) 带了一个 \(x^{n - m + 1}\) 作为系数,于是将上式放在 \(\bmod\ x^{n - m + 1}\) 意义下计算即可将这一项消去。又因为 \(\deg Q' = n - m \lt n - m + 1\),故 \(Q'(x)\) 的计算不会受到影响。
综上,有 :
\[ F'(x) = Q'(x)G'(x) \pmod {x ^ {n - m + 1}} \tag{5.3.4} \]使用多项式求逆即可计算 \(Q(x)\),将其带入定义式可以得到 \(R(x)\)。
时间复杂度 \(\Theta(n \log n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e5, mod = 998244353;
int n, m, len, ln, F[N], G[N], rev[N], FR[N], GR[N], Q[N], R[N], FI[N], GI[N];
int Pow (int a, int k) {
int res = 1;
for ( ; k; a = 1ll * a * a % mod, k >>= 1)
if(k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int len, int *F, bool type) {
rep(i, 0, len - 1) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
void INV (int n, int *F) {
rep(i, 0, n << 2) FI[i] = GI[i] = 0;
GI[0] = Pow(F[0], mod - 2);
for (int len = 4, ln = 2; len <= n << 2; len <<= 1, ln++) {
rep(i, 0, (len >> 1) - 1) FI[i] = F[i];
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, GI, true), NTT(len, FI, true);
rep(i, 0, len - 1) GI[i] = 1ll * GI[i] * (2 - 1ll * GI[i] * FI[i] % mod + mod) % mod;
NTT(len, GI, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, (len >> 1) - 1) GI[i] = 1ll * GI[i] * Inv % mod;
rep(i, len >> 1, len - 1) GI[i] = 0;
}
rep(i, 0, n) F[i] = GI[i];
}
void DIV (int n, int m, int *F, int *G) {
rep(i, 0, n) FR[i] = F[n - i];
rep(i, 0, m) GR[i] = G[m - i];
rep(i, n - m + 1, m) GR[i] = 0;
INV(n - m, GR);
for (len = 1, ln = 0; len <= 2 * n - m; len <<= 1, ln++);
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, FR, true), NTT(len, GR, true);
rep(i, 0, len - 1) FR[i] = 1ll * FR[i] * GR[i] % mod;
NTT(len, FR, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, n - m) Q[i] = 1ll * FR[n - m - i] * Inv % mod;
rep(i, 0, n - m) printf("%d ", Q[i]); puts("");
for (len = 1, ln = 0; len <= n; len <<= 1, ln++);
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, G, true), NTT(len, Q, true);
rep(i, 0, len - 1) G[i] = 1ll * G[i] * Q[i] % mod;
NTT(len, G, false);
Inv = Pow(len, mod - 2);
rep(i, 0, m - 1) R[i] = (F[i] - 1ll * G[i] * Inv % mod + mod) % mod;
rep(i, 0, m - 1) printf("%d ", R[i]);
}
int main () {
scanf("%d%d", &n, &m);
rep(i, 0, n) scanf("%d", &F[i]);
rep(i, 0, m) scanf("%d", &G[i]);
DIV(n, m, F, G);
return 0;
}
多项式指对函数
两者均由麦克劳林级数定义 :
\[ \begin{aligned} \ln F(x) &= -\sum_{i \geqslant 1} \frac{[1 - F(x)] ^ i}{i}\\ \exp F(x) &= \sum _{i \geqslant 0} \frac{F^i(x)}{i!} \end{aligned} \]之所以用麦克劳林级数这么复杂的方式,是因为这样就可以只用加法和乘法定义多项式指对函数了。
众所周知有多项式的求导 :
\[ F'(x) = \sum _{i \geqslant 1} iF_i\cdot x ^ {i - 1} \tag{5.4.2} \]还有多项式的积分 :
\[ F(x) = C + \sum _{i \geqslant 0} \frac{F'_i}{i} \cdot x ^ {i + 1} \tag{5.4.3} \]多项式对数函数
首先,对于多项式 \(F(x)\),若 \(\ln F(x)\) 存在,则按照其定义,必须满足 :
\[ [x ^ 0]F(x) = 1 \tag{5.4.4} \]先对 \(\ln F(x)\) 求导 :
\[ \frac{\mathrm{d}\ln F(x)}{\mathrm{d} x} \equiv \frac{F'(x)}{F(x)} \pmod {x ^ n} \tag{5.4.5} \]再对其积分 :
\[ \ln F(x) \equiv \int \mathrm{d}\ln F(x) \equiv \int \frac{F'(x)}{F(x)}\mathrm{d} x \pmod {x ^ n} \tag{5.4.6} \]多项式的求导和积分时间复杂度均为 \(\Theta(n)\),多项式求逆和乘法的时间复杂度为 \(\Theta(n \log n)\),所以求解多项式对数函数的时间复杂度为 \(\Theta(n \log n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 4e5 + 10, mod = 998244353;
int n, m, f[N], F[N], rev[N], FI[N], GI[N], iF[N], lnF[N];
int Pow (int a, int k) {
int res = 1;
for (; k; a = 1ll * a * a % mod, k >>= 1)
if (k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int len, int *F, bool type) {
rep(i, 0, len - 1) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
void INV (int n, int *F, int *iF) {
rep(i, 0, n << 2) GI[i] = FI[i] = 0;
GI[0] = Pow(F[0], mod - 2);
for (int len = 4, ln = 2; len <= n << 2; len <<= 1, ln++) {
rep(i, 0, (len >> 1) - 1) FI[i] = F[i];
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, GI, true), NTT(len, FI, true);
rep(i, 0, len - 1) GI[i] = 1ll * GI[i] * (2 - 1ll * GI[i] * FI[i] % mod + mod) % mod;
NTT(len, GI, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, (len >> 1) - 1) GI[i] = 1ll * GI[i] * Inv % mod;
rep(i, len >> 1, len - 1) GI[i] = 0;
}
rep(i, 0, n) iF[i] = GI[i];
rep(i, n + 1, n << 2) iF[i] = 0;
}
void LN (int n, int *F, int *lnF) {
rep(i, 0, n - 1) lnF[i] = 1ll * (i + 1) * F[i + 1] % mod;
rep(i, n, n << 2) lnF[i] = 0;
INV(n, F, iF);
int len = 1, ln = 0;
while (len < n << 1) len <<= 1, ln++;
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, lnF, true), NTT(len, iF, true);
rep(i, 0, len - 1) lnF[i] = 1ll * lnF[i] * iF[i] % mod;
NTT(len, lnF, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, n - 1) lnF[i] = 1ll * lnF[i] * Inv % mod;
rep(i, n, n << 2) lnF[i] = 0;
dep(i, n, 1) lnF[i] = 1ll * Pow(i, mod - 2) * lnF[i - 1] % mod;
lnF[0] = 0;
}
int main () {
cin >> n, n--;
rep(i, 0, n) cin >> F[i];
LN(n, F, lnF);
rep(i, 0, n) cout << lnF[i] << " ";
return 0;
}
多项式指数函数
首先,对于多项式 \(F(x)\),若 \(\exp F(x)\) 存在,则按照其定义,必须满足 :
\[ [x ^ 0] F(x) = 1 \tag{5.4.7} \]否则 \(\exp F(x)\) 的常数项不收敛。
对 \(\exp F(x)\) 求导可得 :
\[ \frac{\mathrm{d} \exp F(x)}{\mathrm{d}x} \equiv F'(x) \exp F(x) \pmod {x ^ n} \tag{5.4.8} \]若记 \(\exp F(x)\) 为 \(G(x)\),则可以得到 :
\[ G'(x) \equiv F'(x)G(x) \pmod {x ^ n} \tag{5.4.9} \]用系数关系表示为 :
\[ G'_n = \sum _{i = 0} ^ n F'_i G_{n - i} \tag{5.4.10} \]将导函数积回来,可以得到 :
\[ G_{n + 1} = \frac{1}{n + 1}\sum _{i = 0} ^ n (i + 1)F_{i + 1}G_{n - i} \tag{5.4.11} \]上式的递推可以用分治多项式乘法优化至 \(\Theta(n \log^{2} n)\)。
多项式牛顿迭代
定义多项式的复合 :
\[ F \circ G (x) = \sum _{i\geqslant 0} F_i G^i(x) \tag{5.5.1} \]已知函数 \(G(x)\),求多项式 \(F(x) \pmod {x^{n}}\),满足 \(G \circ F(x) = 0\)。
首先当多项式的界为 \(\bmod\ x\) 时,可以手算出 \(F(x) \pmod x\)。
假设现在已经求得 \(\bmod\ x^{n / 2}\) 意义下的解 \(F_{0}(x)\),要进一步求出 \(\bmod\ x^{n}\) 意义下的解 \(F(x)\)。
众所周知有泰勒公式 :
\[ f(x) = \sum _{i = 0} ^ {\infty} \frac{f^{(i)}(x_0)}{i!}(x - x_0) ^ i \tag{5.5.2} \]将 \(G[F(x)]\) 在 \(F_{0}(x)\) 处进行泰勒展开,有 :
\[ \sum _{i = 0} ^ {\infty} \frac{G^{(i)}[F_0(x)]}{i!}[F(x) - F_0(x)] ^ i \equiv 0 \pmod {x ^ n} \tag{5.5.3} \]由于当 \(i\) 取大于 \(1\) 的值时 \([F(x) - F_{0}(x)]^{i}\) 的最低次项都会超过 \(x^{n}\),所以 :
\[ G[F_0(x)] + G'[F_0(x)][F(x) - F_0(x)] \equiv 0 \pmod {x ^ n} \tag{5.5.4} \]化简得到牛顿迭代的经典公式 :
\[ F(x) = F_0(x) - \frac{G[F_0(x)]}{G'[F_0(x)]} \pmod {x ^ n} \tag{5.5.5} \]使用多项式牛顿迭代也可以解决一些其它的多项式基本操作。
多项式求逆
设需要求逆的函数为 \(H(x)\),\(F(x) = H^{-1}(x)\),有方程 :
\[ G \circ F(x) \equiv \frac{1}{F(x)} - H(x) \equiv 0 \pmod {x ^ n} \tag{5.5.6} \]运用式 \((5.5.5)\) 可得 :
\[ F(x) \equiv F_0(x) - \frac{\frac{1}{F_0(x)} - H(x)}{-\frac{1}{F_0^{2}(x)}} \equiv 2F_0(x) - F_0^2(x)H(x) \pmod {x ^ n} \tag{5.5.7} \]时间复杂度 \(T(n) = T\left(\frac{n}{2}\right) + \Theta(n \log n) = \Theta(n \log n)\)。
多项式开方
设需要开方的函数为 \(H(x)\),\(F(x) = H^{\frac{1}{2}}(x)\),有方程 :
\[ G \circ F(x) \equiv F^2(x) - H(x) \equiv 0 \pmod {x ^ n} \tag{5.5.8} \]运用式 \((5.5.5)\) 可得 :
\[ F(x) \equiv F_0(x) - \frac{F_0^2(x) - H(x)}{2F_0(x)} \equiv 2F_0^{-1}(x)[F_0^2(x) + H(x)] \pmod {x ^ n} \tag{5.5.9} \]时间复杂度 \(T(n) = T\left(\frac{n}{2}\right) + \Theta(n \log n) = \Theta(n \log n)\)。
多项式指数函数
设需要求 \(\exp\) 的函数为 \(H(x)\),\(F(x) = \exp H(x)\),有方程 :
\[ G \circ F(x) \equiv \ln F(x) - H(x) \equiv 0 \pmod {x ^ n} \tag{5.5.10} \]运用式 \((5.5.5)\) 可得 :
\[ F(x) \equiv F_0(x) - \frac{\ln F_0(x) - H(x)}{\frac{1}{F_0(x)}} \equiv F_0(x)[1 - \ln F_0(x) + H(x)] \pmod {x ^ n} \tag{5.5.11} \]时间复杂度 \(T(n) = T\left(\frac{n}{2}\right) + \Theta(n \log n) = \Theta(n \log n)\)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for (int i = l; i <= r; i++)
#define dep(i, r, l) for (int i = r; i >= l; i--)
const int N = 3e5, mod = 998244353;
int n, k, F[N], rev[N], FI[N], GI[N], iF[N], FE[N], GE[N], lnF[N];
int Pow (int a, int k) {
int res = 1;
for (; k; a = 1ll * a * a % mod, k >>= 1)
if (k & 1) res = 1ll * res * a % mod;
return res;
}
void NTT (int len, int *F, bool type) {
rep(i, 0, len - 1) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int k = 1; k < len; k <<= 1) {
int eps = Pow(type ? 3 : 332748118, (mod - 1) / (k << 1));
for (int i = 0; i < len; i += (k << 1))
for (int j = i, g = 1; j < i + k; j++, g = 1ll * g * eps % mod) {
int tmp1 = F[j], tmp2 = 1ll * g * F[j + k] % mod;
F[j] = tmp1 + tmp2 >= mod ? tmp1 + tmp2 - mod : tmp1 + tmp2;
F[j + k] = tmp1 - tmp2 < 0 ? tmp1 - tmp2 + mod : tmp1 - tmp2;
}
}
}
void INV (int n, int *F) {
rep(i, 0, n << 2) FI[i] = GI[i] = 0;
GI[0] = Pow(F[0], mod - 2);
for (int len = 4, ln = 2; len <= n << 2; len <<= 1, ln++) {
rep(i, 0, (len >> 1) - 1) FI[i] = F[i];
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, GI, true), NTT(len, FI, true);
rep(i, 0, len - 1) GI[i] = 1ll * GI[i] * (2 - 1ll * GI[i] * FI[i] % mod + mod) % mod;
NTT(len, GI, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, (len >> 1) - 1) GI[i] = 1ll * GI[i] * Inv % mod;
rep(i, len >> 1, len - 1) GI[i] = 0;
}
rep(i, 0, n) iF[i] = GI[i];
}
void LN (int n, int *F) {
INV(n, F);
rep(i, 0, n - 1) F[i] = 1ll * (i + 1) * F[i + 1] % mod;
F[n] = 0;
int len = 1, ln = 0;
for (; len < n << 1; len <<= 1, ln++);
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, F, true), NTT(len, iF, true);
rep(i, 0, len - 1) F[i] = 1ll * F[i] * iF[i] % mod;
NTT(len, F, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, n - 1) F[i] = 1ll * F[i] * Inv % mod;
rep(i, n, len - 1) F[i] = 0;
dep(i, n, 1) F[i] = 1ll * Pow(i, mod - 2) * F[i - 1] % mod;
F[0] = 0;
}
void EXP (int n, int *F) {
rep(i, 0, (n << 2)) FE[i] = GE[i] = 0;
GE[0] = exp(F[0]);
for (int len = 2, ln = 1; len <= n << 2; len <<= 1, ln++) {
rep(i, 0, (len >> 1) - 1) FE[i] = F[i], lnF[i] = GE[i];
LN((len >> 1) - 1, lnF);
rep(i, 0, len - 1) rev[i] = (rev[i >> 1] >> 1) + (i & 1) * (1 << ln - 1);
NTT(len, GE, true), NTT(len, FE, true), NTT(len, lnF, true);
rep(i, 0, len - 1) GE[i] = 1ll * GE[i] * (1 - lnF[i] + FE[i] + mod) % mod;
NTT(len, GE, false);
int Inv = Pow(len, mod - 2);
rep(i, 0, len - 1) GE[i] = 1ll * GE[i] * Inv % mod;
}
rep(i, 0, n) F[i] = GE[i];
}
void read (int &x) {
x = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9')
x = (10ll * x + (c ^ 48)) % mod, c = getchar();
}
int main () {
read(n), n--;
rep(i, 0, n) read(F[i]);
EXP(n, F);
rep(i, 0, n) printf("%d ", F[i]);
return 0;
}
多项式三角函数
三角函数
给定多项式 \(F(x)\),求模 \(x^{n}\) 意义下的 \(\sin F(x), \cos F(x)\) 与 \(\tan F(x)\)。
首先由欧拉公式 \(\left(e^{i x}=\cos x+i \sin x\right)\) 可以得到三角函数的另一个表达式 :
\[ \begin{aligned} &\sin x=\frac{e^{i x}-e^{-i x}}{2 i} \\ &\cos x=\frac{e^{i x}+e^{-i x}}{2} \end{aligned} \]那么代入 \(F(x)\) 就有 :
\[ \begin{aligned} \sin F(x) &=\frac{\exp [iF(x)] - \exp [-iF(x)]}{2i} \\ \cos F(x) &=\frac{\exp [iF(x)] + \exp [-iF(x)]}{2} \end{aligned} \]直接按上述表达式编写程序即可得到模 \(x^{n}\) 意义下的 \(\sin F(x)\) 与 \(\cos F(x)\)。
再由 \(\tan F(x)=\frac{\sin F(x)}{\cos F(x)}\) 可求得 \(\tan F(x)\)。
时间复杂度 \(\Theta(n \log n)\)。
反三角函数
仿照求多项式对数函数的方法,对反三角函数求导再积分可得 :
\[ \begin{aligned} \frac{\mathrm{d}}{\mathrm{d} x} \arcsin x &=\frac{1}{\sqrt{1-x^{2}}} \\ \arcsin x &=\int \frac{1}{\sqrt{1-x^{2}}} \mathrm{~d} x \\ \frac{\mathrm{d}}{\mathrm{d} x} \arccos x &=-\frac{1}{\sqrt{1-x^{2}}} \\ \arccos x &=-\int \frac{1}{\sqrt{1-x^{2}}} \mathrm{~d} x \\ \frac{\mathrm{d}}{\mathrm{d} x} \arctan x &=\frac \arctan x &=\int \frac{1}{1+x^{2}} \mathrm{~d} x \end{aligned} \]那么代入 \(F(x)\) 就有:
\[ \begin{aligned} \frac{\mathrm{d}}{\mathrm{d} x} \arcsin F(x) &=\frac{F^{\prime}(x)}{\sqrt{1-F^{2}(x)}} \\ \arcsin F(x) &=\int \frac{F^{\prime}(x)}{\sqrt{1-F^{2}(x)}} \mathrm{d} x \\ \frac{\mathrm{d}}{\mathrm{d} x} \arccos F(x) &=-\frac{F^{\prime}(x)}{\sqrt{1-F^{2}(x)}} \\ \arccos F(x) &=-\int \frac{F^{\prime}(x)}{\sqrt{1-F^{2}(x)}} \mathrm{d} x \\ \frac{\mathrm{d}}{\mathrm{d} x} \arctan F(x) &=\frac{F^{\prime}(x)}{1+F^{2}(x)} \\ \arctan F(x) &=\int \frac{F^{\prime}(x)}{1+F^{2}(x)} \mathrm{d} x \end{aligned} \]直接按式子求就可以了。
时间复杂度均为 \(\Theta(n \log n)\)。
