Appearance
仅含【洛谷】紫以下多项式。
FFT
设两多项式长度分别为 init(L);根表和 rev 按
点击展开代码
cpp
const ldb pi = acos(-1.0);
struct Complex {
ldb x, y;
Complex operator+(const Complex &t) const { return {x + t.x, y + t.y}; }
Complex operator-(const Complex &t) const { return {x - t.x, y - t.y}; }
Complex operator*(const Complex &t) const {
return {x * t.x - y * t.y, x * t.y + y * t.x};
}
};
vector<int> rev;
vector<array<Complex, 2>> g;
void init(int n) { // n 为预先确定的最大变换长度
assert(n > 0 && (n & (n - 1)) == 0);
rev.resize(n), g.resize(n);
for (int mid = 1; mid < n; mid <<= 1) {
g[mid][0] = Complex({cos(pi / mid), -sin(pi / mid)});
g[mid][1] = Complex({cos(pi / mid), sin(pi / mid)});
}
}
void fft(vector<Complex> &a, int sign, int tot) {
assert((int)rev.size() >= tot && (int)g.size() >= tot);
for (int i = 0; i < tot; i++)
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int mid = 1; mid < tot; mid <<= 1) {
auto w1 = g[mid][(sign + 1) / 2];
for (int i = 0; i < tot; i += (mid << 1)) {
auto wk = Complex({1, 0});
for (int j = 0; j < mid; j++, wk = wk * w1) {
auto x = a[i + j], y = wk * a[i + j + mid];
a[i + j] = x + y, a[i + j + mid] = x - y;
}
}
}
}NTT
常见 NTT 模数及原根:
NTT 的变换长度 int 模板适用于 __int128 或安全模乘,不能直接套用该模板。整数快速幂 qz 内的乘法也必须使用 64 位中间量。
点击展开代码
cpp
vector<int> rev, g, inv_g;
void init(int n) { // n 为预先确定的最大变换长度
assert(n > 0 && (n & (n - 1)) == 0 && (mod - 1) % n == 0);
rev.resize(n), g.resize(n + 1), inv_g.resize(n + 1);
for (int mid = 1; mid < n; mid <<= 1) {
g[mid << 1] = qz(G, (mod - 1) / (mid << 1));
inv_g[mid << 1] = qz(inv_G, (mod - 1) / (mid << 1));
}
}
void ntt(vector<int> &a, int sign, int tot) {
assert((int)rev.size() >= tot && (int)g.size() > tot);
for (int i = 0; i < tot; i++)
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int mid = 1; mid < tot; mid <<= 1) {
int g1 = ~sign ? g[mid << 1] : inv_g[mid << 1];
for (int i = 0; i < tot; i += (mid << 1)) {
int gk = 1;
for (int j = 0; j < mid; j++, gk = 1ll * gk * g1 % mod) {
int x = a[i + j], y = 1ll * gk * a[i + j + mid] % mod;
a[i + j] = (x + 1ll * y) % mod;
a[i + j + mid] = (x - 1ll * y + mod) % mod;
}
}
}
if (sign == -1) {
int inv = qz(tot, mod - 2);
for (int i = 0; i < tot; i++)
a[i] = 1ll * a[i] * inv % mod;
}
}多项式乘法
点击展开代码
cpp
vector<int> mul(vector<int> a, vector<int> b) {
int tot = 0, bit = 0;
int n = a.size(), m = b.size();
while ((1 << bit) <= n - 1 + m - 1)
bit++;
tot = 1 << bit;
assert((int)rev.size() >= tot && (int)g.size() > tot);
a.resize(tot), b.resize(tot);
vector<int> c(tot);
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
ntt(a, 1, tot), ntt(b, 1, tot);
for (int i = 0; i < tot; i++)
c[i] = 1ll * a[i] * b[i] % mod;
ntt(c, -1, tot);
c.resize(n + m - 1);
return c;
}FFT 处理多项式乘法时,特殊处理:
点击展开代码
cpp
vector<int> mul(vector<int> a, vector<int> b) {
vector<Complex> A(a.size()), B(b.size());
for (int i = 0; i < A.size(); i++)
A[i].x = a[i];
for (int i = 0; i < B.size(); i++)
B[i].x = b[i];
int tot = 0, bit = 0;
int n = A.size(), m = B.size();
while ((1 << bit) <= n - 1 + m - 1)
bit++;
tot = 1 << bit;
assert((int)rev.size() >= tot && (int)g.size() >= tot);
A.resize(tot), B.resize(tot);
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
fft(A, 1, tot), fft(B, 1, tot);
for (int i = 0; i < tot; i++)
A[i] = A[i] * B[i];
fft(A, -1, tot);
vector<int> c(n + m - 1);
for (int i = 0; i < n + m - 1; i++)
c[i] = (int)llround(A[i].x / tot);
return c;
}这里假设输入系数为整数,且真实卷积系数与浮点计算结果的绝对误差小于 llround 唯一还原。若输入系数绝对值分别不超过 int 范围。长度或系数过大时应使用拆系数 FFT、NTT/CRT,不能把浮点舍入视为无条件精确。
任意模数多项式乘法
设两多项式的长度分别为
一种朴素实现是三模数 NTT,需要
具体实现为选取三个两两互质的 NTT 模数,进行三次 NTT,得到同余方程组,再用 CRT 或 Garner 算法合并。设组合模数为
三个约 m1 * m2 * m3。实现时应使用 __int128、安全模乘,或用 Garner 算法直接在目标模数下合并,并保证每一步乘法的中间类型足够宽。
这里仅提供使用的三个 NTT 模数。
点击展开代码
cpp
const int mod[3] = {998244353, 469762049, 1004535809},
inv_G[3] = {332748118, 156587350, 334845270}, G = 3;拆系数 FFT,需要
多项式乘法逆
以下在素数模数
时间复杂度:
点击展开代码
cpp
vector<int> inv(vector<int> &a) {
int n = a.size();
vector<int> A, B, b = {qz(a[0], mod - 2)};
for (int len = 1, tot; len < (n << 1); len <<= 1) {
tot = len << 1;
A.resize(tot), B.resize(tot);
for (int i = 0; i < len; i++) {
A[i] = i < a.size() ? a[i] : 0, B[i] = i < b.size() ? b[i] : 0;
}
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
ntt(A, 1, tot), ntt(B, 1, tot);
b.resize(tot);
for (int i = 0; i < tot; i++)
b[i] = (2ll - 1ll * A[i] * B[i] % mod + mod) * B[i] % mod;
ntt(b, -1, tot);
b.resize(len);
}
b.resize(n);
return b;
}多项式开根
以下均在奇素数模数下计算
点击展开代码
cpp
vector<int> sqrt(vector<int> &a) {
int n = a.size();
vector<int> A, B, b = {1};
for (int len = 1, tot; len < (n << 1); len <<= 1) {
tot = len << 1;
A.resize(tot);
for (int i = 0; i < len; i++)
A[i] = i < a.size() ? a[i] : 0;
b.resize(len);
B = inv(b);
B.resize(tot);
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
ntt(A, 1, tot), ntt(B, 1, tot);
for (int i = 0; i < tot; i++)
A[i] = 1ll * A[i] * B[i] % mod;
ntt(A, -1, tot);
for (int i = 0; i < len; i++)
b[i] = (1ll * b[i] + A[i]) * inv_2 % mod;
}
b.resize(n);
return b;
}若 b = {r};选择
点击展开代码
cpp
int I_mul_I;
struct Complex {
int real, imag;
Complex(int real = 0, int imag = 0) : real(real), imag(imag) {}
bool operator==(const Complex &x) const {
return x.real == real && x.imag == imag;
}
Complex operator*(const Complex &x) const {
return Complex((1ll * x.real * real + 1ll * I_mul_I * x.imag % mod * imag) % mod,
(1ll * x.imag * real + 1ll * x.real * imag) % mod);
}
};
Complex qz(Complex x, int y) {
Complex res = 1;
for (; y; y >>= 1) {
if (y & 1)
res = res * x;
x = x * x;
}
return res;
}
bool check(int x) { return qz(x, mod - 1 >> 1) == 1; }
int solve(int n) { // 求解 n 的二次剩余
if (!n)
return 0;
if (!check(n))
return -1;
int a = rand() % mod;
while (!a || check((1ll * a * a + mod - n) % mod))
a = rand() % mod;
I_mul_I = (1ll * a * a + mod - n) % mod;
return qz(Complex(a, 1), mod + 1 >> 1).real;
}多项式除法
以下假设系数数组已经去除无意义的高位零,且除式
点击展开代码
cpp
pair<vector<int>, vector<int>> divide(vector<int> a, vector<int> b) {
int n = a.size(), m = b.size();
if (n < m)
return {{0}, a};
auto ar = a, br = b;
reverse(ar.begin(), ar.end());
reverse(br.begin(), br.end());
ar.resize(n - m + 1), br.resize(n - m + 1);
auto now = inv(br);
auto cr = mul(ar, now);
cr.resize(n - m + 1);
reverse(cr.begin(), cr.end());
auto cur = subtract(a, mul(b, cr));
return {cr, cur};
}多项式 ln
以下按
点击展开代码
cpp
vector<int> ln(vector<int> &a) {
int n = a.size();
vector<int> A(n - 1);
for (int i = 1; i < n; i++)
A[i - 1] = 1ll * a[i] * i % mod;
auto B = inv(a);
int bit = 0;
while ((1 << bit) < (n << 1))
bit++;
int tot = 1 << bit;
A.resize(tot), B.resize(tot);
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
ntt(A, 1, tot), ntt(B, 1, tot);
for (int i = 0; i < tot; i++)
A[i] = 1ll * A[i] * B[i] % mod;
ntt(A, -1, tot);
vector<int> b(n);
for (int i = 1; i < n; i++)
b[i] = 1ll * A[i - 1] * iv[i] % mod; // i 的逆元
return b;
}多项式 exp
以下计算
点击展开代码
cpp
vector<int> exp(vector<int> &a) {
int n = a.size();
vector<int> b = {1}, A, B;
for (int len = 1, tot; len < (n << 1); len <<= 1) {
tot = len << 1;
A.resize(tot);
for (int i = 0; i < len; i++)
A[i] = i < n ? a[i] : 0;
b.resize(len);
B = ln(b);
b.resize(tot), B.resize(tot);
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
ntt(b, 1, tot), ntt(A, 1, tot), ntt(B, 1, tot);
for (int i = 0; i < tot; i++)
b[i] = (1ll - B[i] + A[i] + mod) * b[i] % mod;
ntt(b, -1, tot);
b.resize(len);
}
b.resize(n);
return b;
}多项式幂
以下约定
先考虑
在素数模数
此时传给 exp 的标量
也可以直接快速幂,时间复杂度为
点击展开代码
cpp
vector<int> qz(vector<int> &a, int k) {
int n = a.size();
auto x = a;
vector<int> res = {1};
int bit = 1;
while (bit < (n << 1))
bit <<= 1;
x.resize(bit), res.resize(bit);
int tot = bit;
for (int i = 0; i < tot; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (tot >> 1));
for (; k; k >>= 1) {
x.resize(tot), ntt(x, 1, tot);
if (k & 1) {
res.resize(tot), ntt(res, 1, tot);
for (int i = 0; i < tot; i++) {
res[i] = 1ll * res[i] * x[i] % mod;
}
ntt(res, -1, tot), res.resize(n);
}
for (int i = 0; i < tot; i++) {
x[i] = 1ll * x[i] * x[i] % mod;
}
ntt(x, -1, tot), x.resize(n);
}
res.resize(n);
return res;
}ln + exp,时间复杂度:
点击展开代码
cpp
vector<int> qz(vector<int> &a, int k) {
auto A = ln(a);
int n = a.size();
for (int i = 0; i < n; i++)
A[i] = 1ll * A[i] * k % mod;
return exp(A);
}一般情况下,设
从而
必须先用原始指数判断
在素数模数 ln + exp 计算
拉格朗日插值
拉格朗日插值
插值公式:
按照公式计算即可。
点击展开代码
cpp
int f(vector<int> &x, vector<int> &y, int k) {
int n = x.size();
int ans = 0;
for (int i = 0; i < n; i++) {
int fz = y[i], fm = 1;
for (int j = 0; j < n; j++) {
if (i == j)
continue;
fz = 1ll * fz * (k - x[j] + mod) % mod;
fm = 1ll * fm * (x[i] - x[j] + mod) % mod;
}
ans = (ans + 1ll * fz * qz(fm, mod - 2)) % mod;
}
if (ans < 0)
ans += mod;
return ans;
}特别地,若给定的坐标连续,可以
具体而言,若给定
若
并统一使用
其中
