拉格朗日插值
插值问题形如
一般插值
容易构造出 $f(x)$ 即为下式
正确性显然
容易证明多项式次数至少为 $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;
}
点值连续的插值
当给定的点值连续时
考虑计算分子
考虑计算分母
全部带入可将式 $(1.2.1)$ 改写为
时间复杂度 $\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)$
将式 $(1.3.1)$ 和 $(1.3.2)$ 带入式 $(1.1.1)$ 可得
如此一来
还原插值多项式的系数
考虑对式 $(1.1.1)$ 作变形
可以 $\Theta(n^2)$ 预处理 $\prod_{i = 1}^n (x - x_i)$
还原插值多项式系数的时间复杂度为 $\Theta(n^2)$
自然数幂前缀和
有定理
$\rm Proof :$
设 $f_k(n) = \sum \limits _{i = 1} ^n i ^ k$
当 $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$
容易发现
归纳可得
于是可以使用点值连续的拉格朗日插值计算 $\sum \limits_{i = 1} ^n i^k$
快速傅里叶变换
快速傅里叶变换 $\rm (Fast~Fourier~Transformation,FFT)$ 被用来在 $\Theta(n\log n)$ 的时间复杂度下求两个多项式的乘积
若称 $h(x) = f(x) \times g(x)$
单位根
众所周知
根据高中课本上所讲的
考虑方程 $x ^ n = 1$
之所以会在 $\rm FFT$ 中用到单位根
- 折半性质
:
$\rm Proof :$ 显然有 $\omega_n ^k = (\omega _n ^1) ^k$ 和 $\omega _n ^ 1 = \omega _{2n} ^2$
- 对称性质
:
$\rm Proof :$ 这里定义 $-z$ 为 $z$ 关于原点的对称点
- 求和性质
:
$\rm Proof :$
首先使用等比数列求和公式
考虑分类讨论
- 当 $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$ 个点值
对于一个多项式 $F(x) = a_0 + a_1 x + a_2 x^2 + \cdots + a_{n - 1} x ^ {n - 1}$
于是有 $F(x) = F_1(x^2) + xF_2(x^2)$
将 $\omega_n^k$ 带入可得
根据式 $(2.1.2)$ 可得
考虑分类讨论
- 对于 $k < \frac{n}{2} $
只需要直接递归处理 $F_1$ 和 $F_2$ 即可, 。 - 对于 $k \geqslant \frac{n}{2}$
我们不妨它为 $k + \frac{n}{2}~(k < \frac{n}{2})$, 于是有, :
根据式 $(2.1.2)$ 和 $(2.1.3)$ 可得
观察发现
因为最多递归 $\log n$ 次
离散傅里叶逆变换
离散傅里叶逆变换 $\rm (Inverse~Discrete~Fourier~Transform,IDFT)$ 被用于将多项式的点值表示还原成系数表示
在 $\rm DFT$ 过程中我们已经将待乘函数 $f(x)$ 和 $g(x)$ 的点值表示分别求了出来
设 $\{G_n\}$ 为 $F(x)$ 在 $\rm DFT$ 过程中求出的点值序列
此处有反演结论
$\rm Proof :$
将式 $(2.1.4)$ 带入 $(2.3.3)$ 可得
故利用式 $(2.3.2)$ 将 $\{G_n\}$ 带入 $\rm DFT$ 过程
$\rm IDFT$ 过程的时间复杂度为 $\Theta(n\log n)$
至此
位逆序置换
位逆序置换 $\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(0) = 0$
求解 $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$ 对单位根的依赖
值得庆幸的是
原根
原根的定义如下
如果有 $a^n \equiv 1 \pmod p$
我们尝试用原根代替单位根进行运算
- 不重性质
:
$\rm Proof :$ 若存在 $0\leqslant i < j < \varphi (p)$ 满足 $g^i \equiv g^j \pmod p$
- 折半性质
:
定义 $g_n^1 = g^{\frac{p - 1}{n}},g_n^k = (g_n^1)^k$ 则
$\rm Proof :$ 由式 $(3.1.1)$ 可得 $g_n^0,g_n^1,g_n^2,\cdots,g_n^{n - 1}$ 互不相同
- 对称性质
:
$\rm Proof :$
参照式 $(2.1.3)$ 的证明方法
所以 $g_{2n}^n \equiv 1~or-1 \pmod p$
根据式 $(3.1.1)$
- 求和性质
:
$\rm Proof :$ 参照式 $(2.1.4)$ 的证明
有了以上 $4$ 条性质
另外
比较常用的 $\rm NTT$ 模数是 $998244353 = 119 \times 2 ^ {23} + 1$
#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;
}
$\rm Chirp~Z$ 变换
$\rm Chirp~Z-Transform$ 又称 $\rm Bluestein$ 算法
给定 $n$ 次多项式 $F(x)$
考虑组合恒等式
$\rm Proof :$
于是有
设 $f(x) = c ^ {\binom{x}{2}}, g(x) = a_xc^{-\binom{x}{2}}$
可以使用减法卷积来计算
具体来说
计算式 $(3.2.5)$ 时可以先对 $f(x)$ 和 $g(x)$ 求和卷积
假设 $f(x)$ 的次数为 $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, 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)$ 的所有项
这时候可以考虑采用 $\rm CDQ$ 分治的思想
分治多项式乘法的时间复杂度为 $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)$
构造 $\rm FWT$ 函数
在 $\rm FFT$ 中
设 $F' = FWT(F)$ 表示幂级数 $F$ 经过 $\rm FWT$ 变换后得到的幂级数
容易发现
由于 $\rm DFT$ 是线性变换
因此
根据 $FWT(A) \cdot FWT(B) = FWT(C)$ 可以得到
根据 $A \times B = C$ 可以得到
对比式 $(4.1.8)$ 中两边系数可以发现只需要满足
假设我们已经求出了 $k_{0, 0},k_{0,1},k_{1,0},k_{1,1}$
求解 $\rm FWT$ 变换
现在我们需要快速求解下式
我们考虑按位折半计算
设 $F^0$ 表示幂级数 $F'$ 下标的二进制首位为 $0$ 的部分
- 若 $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$, 。
在该算法中
此外
构造位矩阵
针对不同的位运算
或卷积
首先我们知道
首先可以观察到 $k_{p, i} \times k_{p, i} = k_{p, i}$
然后可以发现 $k_{p, 0} \times k_{p, 1} = k_{p, 1}$
由于右边那种等价于高维前缀和
对于逆变换
与卷积
可以发现与卷积同或卷积几乎完全一样
异或卷积
首先我们知道
因为 $k_{0, 0} \times k_{x, y} = k_{x, y}$
因为 $k_{1, 1} \times k_{1, 1} = k_{1, 0}$
因为 $k_{1, 0} \times k_{1, 1} = k_{1, 1}$
因为 $k_{1, 1} \times k_{1, 1} = k_{1, 0} = 1$
因为 $k_{0,1} \times k_{0, 1} = k_{0, 0} = 1$
因为 $k_{0, 1}$ 和 $k_{1, 1}$ 不能相等
我们采用第二种
对 $k$ 求逆可以得到 $k^{-1} = \begin{bmatrix} \frac{1}{2}&\frac{1}{2}\\\frac{1}{2}&-\frac{1}{2} \end{bmatrix}$
#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$
求解多项式乘法逆元的时候考虑使用倍增的思想
当多项式的界为 $\bmod~x$ 时
设 $G(x) = F^{-1}(x) \pmod {x^n}$
为了能配出 $x ^ n$ 作为模数
因为 $F(x)G(x) = 1$
根据式 $(5.1.3)$ 可以倍增求出 $F^{-1}(x)$
#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)$
与多项式求逆一样
当多项式的界为 $\bmod~x$ 时
设 $G(x) = F^{\frac{1}{2}}(x) \pmod {x ^ n}$
对式 $(5.2.1)$ 两边同时平方并作简单变换
对式子两边同时除以 $4G'^2(x)$
结合 $G(x)$ 的定义
根据式 $(5.2.4)$ 可以倍增求出 $F^{\frac{1}{2}}(x)$
#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)$
可以发现如果能消除 $R(x)$ 的影响则可以直接使用多项式求逆
考虑构造变换
可以发现
设 $n = \deg F,m = \deg G$
将式 $(5.3.1)$ 带入可得
注意到式 $(5.3.3)$ 中 $R'(x)$ 带了一个 $x ^ {n - m + 1}$ 作为系数
综上
使用多项式求逆即可计算 $Q(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;
}
多项式指对函数
两者均由麦克劳林级数定义
之所以用麦克劳林级数这么复杂的方式
众所周知有多项式的求导
还有多项式的积分
多项式对数函数
首先
先对 $\ln F(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 = 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;
}
多项式指数函数
首先
否则 $\exp F(x)$ 的常数项不收敛
对 $\exp F(x)$ 求导可得
若记 $\exp F(x)$ 为 $G(x)$
用系数关系表示为
将导函数积回来
上式的递推可以用分治多项式乘法优化至 $\Theta(n \log ^ 2 n)$
多项式牛顿迭代
定义多项式的复合
已知函数 $G(x)$
首先当多项式的界为 $\bmod~x$ 时
假设现在已经求得 $\bmod~x ^ {n / 2}$ 意义下的解 $F_0(x)$
众所周知有泰勒公式
将 $G[F(x)]$ 在 $F_0(x)$ 处进行泰勒展开
由于当 $i$ 取大于 $1$ 的值时 $[F(x) - F_0(x)] ^ i$ 的最低次项都会超过 $x ^ n$
化简得到牛顿迭代的经典公式
使用多项式牛顿迭代也可以解决一些其它的多项式基本操作
多项式求逆
设需要求逆的函数为 $H(x)$
运用式 $(5.5.5)$ 可得
时间复杂度 $T(n) = T\left(\frac{n}{2}\right) + \Theta(n \log n) = \Theta(n \log n)$
多项式开方
设需要开方的函数为 $H(x)$
运用式 $(5.5.5)$ 可得
时间复杂度 $T(n) = T\left(\frac{n}{2}\right) + \Theta(n \log n) = \Theta(n \log n)$
多项式指数函数
设需要求 $\exp$ 的函数为 $H(x)$
运用式 $(5.5.5)$ 可得
时间复杂度 $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)$
首先由欧拉公式 $\left(e^{i x}=\cos x+i \sin x\right)$ 可以得到三角函数的另一个表达式
那么代入 $F(x)$ 就有 :
直接按上述表达式编写程序即可得到模 $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)$
反三角函数
仿照求多项式对数函数的方法
那么代入 $F(x)$ 就有:
直接按式子求就可以了
时间复杂度均为 $\Theta(n \log n)$