多项式(Poly)笔记
开头先扔板子:多项式板子们
定义
多项式(polynomial)是形如 \(P(x) = \sum \limits_{i = 0}^{n} a_i x ^ i\) 的代数表达式。其中 \(x\) 是一个不定元。
\(\partial(P(x))\) 称为这个多项式的次数。
多项式的基本运算
- 多项式的加减法
\]
\]
- 多项式的乘法
\]
- 多项式除法
这里讨论多项式的带余除法。
可以证明,一定存在唯一的 \(q(x), r(x)\) 满足 \(A(x) = q(x) B(x) + r(x)\),且 \(\partial(r(x)) < \partial(B(x))\)。
\(q(x)\) 称为 \(B(x)\) 除 \(A(x)\) 的商式,\(r(x)\) 称为 \(B(x)\) 除 \(A(x)\) 的余式。记作:
\]
特别的,若 \(r(x) = 0\),则称 \(B(x)\) 整除 \(A(x)\),\(A(x)\) 是 \(B(x)\) 的一个倍式,\(B(x)\) 是 \(A(x)\) 的一个因式。记作 \(B(x) | A(x)\)。
有关多项式的引理
对于 \(n + 1\) 个点可以唯一确定一个 \(n\) 次多项式。
如果 \(f(x), g(x)\) 都是不超过 \(n\) 次的多项式,且它对于 \(n +1\) 个不同的数 \(\alpha_1, \alpha_2 \cdots \alpha_n\) 有相同的值,即 \(\forall i \in [1, n + 1], i \in \mathbb{Z}, f(\alpha_i) = g(\alpha_i)\)。则 \(f(x) = g(x)\)。
多项式的点值表示
如果选取 \(n + 1\) 个不同的数 \(x_0, x_1, \cdots x_n\) 对多项式进行求值,得到 \(A(x_0), A(x_1) \cdots A(x_n)\),那么就称 \(\{(x_i, A(x_i)) \ | \ 0 \le i \le n, i \in \mathbb{Z} \}\) 为 \(A(x)\) 的点值表示。
快速傅里叶变换(FFT)
快速傅里叶变换是借助单位根进行求值和插值,从而快速进行多项式乘法的算法。
单位根
将复平面上的单位圆均分成 \(n\) 份,从 \(x\) 轴数,第 \(i\) 条线与单位圆的交点称为 \(i\) 次单位根,记作 \(\omega_{n}^{i}\)。
根据定义,可以得到:\(\omega_{n}^{1} = i \sin \alpha +\cos \alpha, \alpha = \frac{2 \pi}{n}\)。
根据欧拉恒等式,可以得到:\(\omega_{n}^{1} = e ^ {\frac{2 \pi i}{n}}\)。
由此那么可以得到下面的性质:
\(\omega_{n}^{i} \times \omega_{n}^{j} = \omega_{n}^{i + j}\)
\(\omega_{n}^{i + \frac{n}{2}} = - \omega_{n}^{i}\)
\(\omega_{n}^{i + n} = \omega_{n}^{i}\)
离散傅里叶变换(DFT)
离散傅里叶变换,是将 \(\omega_{n}^{k}, 0 \le k < n\) 代入 \(f(x)\) 和 \(g(x)\) 中求值的过程。
对于朴素的方法,每次代入一个单位根,然后用 \(O(n)\) 的复杂度计算函数值。时间复杂度 \(O(n ^ 2)\)。
离散傅里叶变换利用了单位根的性质巧妙优化了这个过程。离散傅里叶变换过程如下:
首先将 \(f(x)\) 根据次数的奇偶性拆成两部分,分别分为:
e(x) = \sum \limits_{i = 0}^{\frac{n - 2}{2}} a_{2i} x ^ {2i} = a_0 + a_2 x^2 + a_4 x^4 \cdots a_{n - 2} x^{n - 2} \\
o(x) = \sum \limits_{i = 0}^{\frac{n - 2}{2}} a_{2i + 1} x ^ {2i + 1} = a_1 x + a_3 x^3 + a_5 x^5 \cdots a_{n - 1} x^{n - 1}
\end{cases}
\]
设
e'(x) = \sum \limits_{i = 0}^{\frac{n - 2}{2}} a_{2i} x ^ i = a_0 + a_2 x + a_4 x^2 \cdots a_{n - 2} x^{\frac{n - 2}{2}} \\
o'(x) = \sum \limits_{i = 0}^{\frac{n - 2}{2}} a_{2i + 1} x ^ {i} = a_1 + a_3 x^1 + a_5 x^2 \cdots a_{n - 1} x^{\frac{n - 2}{2}}
\end{cases}
\]
则 \(f(x) = e'(x ^ 2) + x o'(x ^ 2)\)。
将 \(\omega_{n}^{k}\) 代入得到:
f(\omega_{n}^{k}) = e'((\omega_{n}^{k}) ^ 2) + \omega_{n}^{k} o'((\omega_{n}^{k}) ^ 2) = e'(\omega_{n}^{2k}) + \omega_{n}^{k} o'(\omega_{n}^{2k}) \\\\
f(\omega_{n}^{k + \frac{n}{2}}) = e'((\omega_{n}^{k+ \frac{n}{2}}) ^ 2) + \omega_{n}^{k+ \frac{n}{2}} o'((\omega_{n}^{k+ \frac{n}{2}}) ^ 2) = e'(\omega_{n}^{2k}) - \omega_{n}^{k} o'(\omega_{n}^{2k})
\end{cases}
\]
然后你发现,\(f(\omega_{n}^{k})\) 和 \(f(\omega_{n}^{k + \frac{n}{2}})\) 仅仅差了一个符号!!!
所以只需要求出 \(e'(x)\) 和 \(o'(x)\) 对 \(\omega^{k}_{n}\)(\(0 \le k \le \frac{n}{2}\))上的取值,就可以推出 \(f(x)\) 在两倍点数上的取值。
每次问题规模缩小一半,因此时间复杂度 \(O(n \log n)\)。
离散傅里叶逆变换(IDFT)
假设对于两个多项式都得到了 \(t = n + m - 1\) 个点值,设为 \(\{(x_i, A(x_i)) \ | \ 0 \le i < t, i \in \mathbb{Z} \}, \{(x_i, B(x_i)) \ | \ 0 \le i < t, i \in \mathbb{Z} \}\)。
那么可以知道,多项式 \(C(x) = A(x) \times B(x)\) 的点值表示一定为:
\]
现在,只需要将这 \(t\) 个点插值回去,就可以得到 \(A(x)B(x)\) 了。
先设这 \(t\) 个点值分别是:\(\{(x_i, v_i) \ | \ 0 \le i < t, i \in \mathbb{Z} \}\),设最后的多项式为 \(C(x) = \sum \limits_{i = 0}^{n + m - 2} c_i x^i\),这里直接给出结论:
\]
由此可见,IDFT 和 DFT 仅仅有一个负号的区别。只要将所有的单位根从 \(\omega_{n}^{k}\) 变成 $ - \omega_{n}^{k}$ 即可。
void FFT(cp a[], int n, int op) {
if (n == 1) return;
cp a1[n], a2[n];
rop(i, 0, n >> 1) a1[i] = a[i << 1], a2[i] = a[(i << 1) + 1];
FFT(a1, n >> 1, op), FFT(a2, n >> 1, op);
cp Wn = {cos(2 * pi / n), op * sin(2 * pi / n)};
cp Wk = {1, 0};
rop(i, 0, n >> 1) {
a[i] = a1[i] + Wk * a2[i];
a[i + (n >> 1)] = a1[i] - Wk * a2[i];
Wk = Wk * Wn;
}
}
void FFT(cp a[], cp b[], int n, int m) {
m = n + m; n = 1;
while (n <= m) n <<= 1;
FFT(a, n, 1); FFT(b, n, 1);
rop(i, 0, n) a[i] = a[i] * b[i];
FFT(a, n, -1);
rep(i, 0, m) a[i].x = a[i].x / n;
}
FFT 优化
- 三次变两次优化
原本的朴素 FFT,将 \(\{a\}, \{b\}\) 两个序列分别求值,乘起来再 IDFT 插值一下,一共跑了三次 FFT。这是不好的。
三次变两次优化是这样的:将原序列合并成一个复数:\(\{a_i + b_i \times i\}\)。做一遍 DFT 把求出的每个函数值平方。因为 \((a + bi) ^ 2 = (a ^ 2 - b ^ 2) + (2abi)\)。因此把虚部取出来以后除以 \(2\) 就是答案。
void FFT(cp a[], int n, int op) {
if (n == 1) return;
cp a1[n], a2[n];
rop(i, 0, n >> 1) a1[i] = a[i << 1], a2[i] = a[(i << 1) + 1];
FFT(a1, n >> 1, op), FFT(a2, n >> 1, op);
cp Wn = {cos(2 * pi / n), op * sin(2 * pi / n)};
cp Wk = {1, 0};
rop(i, 0, n >> 1) {
a[i] = a1[i] + a2[i] * Wk;
a[i + (n >> 1)] = a1[i] - a2[i] * Wk;
Wk = Wk * Wn;
}
}
int main() {
read(n, m);
rep(i, 0, n) scanf("%lf", &a[i].x);
rep(i, 0, m) scanf("%lf", &a[i].y);
m = n + m; n = 1;
while (n <= m) n <<= 1;
FFT(a, n, 1);
rop(i, 0, n) a[i] = a[i] * a[i];
FFT(a, n, -1);
rep(i, 0, m) printf("%d ", (int)(a[i].y / 2 / n + 0.5));
}
- 蝴蝶变换优化
后面再补吧。其实本人感觉这个优化不是那么必要,因为三次变两次实在太快了。
FFT 例题
可以设 \(x = 10\),把 \(a\) 写成 \(A(x) = \sum \limits_{i = 0}^{n} a_i x^i\) 的形式(\(n = \log_{10} a\))。同理可以把 \(b\) 转化为多项式 \(B(x)\)。
这样求两个数相乘就是求 \(A(x) \times B(x)\) 啊。
所以直接 \(O(n \log n)\) 做完了。
给出 \(n\) 个数 \(q_1,q_2, \dots q_n\),定义
\]
\]
对 \(1 \leq i \leq n\),求 \(E_i\) 的值。
首先发现这个除以 \(q_i\) 就是没用。所以可以化简成:
\]
先看前面这个式子。答案就是:
\]
设 \(f(x) = \sum q_i x ^ i, g(x) = \dfrac{1}{i ^ 2} x ^ i\)。可以发现,\(E_j' = (f \times g)[j]\)
再看后面这一块的式子。我们把 \(f(x)\) 的系数翻转,变成 \(f'(x) = \sum q_{n - j + 1} x ^ j\)。那么可以发现 \(E_{j}'' = (f' \times g)[n - j + 1]\)。
跑两次 FFT 就完事了。
首先发现加减相对于两个手环是对称的。因此可以把对一个手环的减法转化成对另一个手环的加法。这样可以假设全是在第一个手环上执行的加减操作。
第一个手环执行了加 \(c\) 的操作,且旋转过之后的序列为 \([x_1, x_2 \cdots x_n]\),第二个手环为 \([y_1, y_2 \cdots y_n]\)。计算差异值并化简,可以得到差异值是:
\(\sum x ^ 2 + \sum y ^ 2 + c ^ 2 n + 2c(\sum x - \sum y) - 2 \sum xy\)
可以发现,这个序列只有最后一项是不定的。
因此将 \(y\) 序列翻转后再复制一倍,与 \(x\) 卷积,答案就是卷积后序列的 \(n + 1 \sim 2n\) 项系数的 \(\max\)。
直接暴力枚举 \(c\),加上前面依托就行了。
快速数论变换(NTT)
快速数论变换就是基于原根的快速傅里叶变换。
首先考虑快速傅里叶变换用到了单位根的什么性质。
\(\omega_{n}^{k}, 0 \le k < n\) 互不相同。
\(\omega_{n}^{k} = \omega_{n}^{k + \frac{n}{2}}\)。
\(\omega_{n}^{k} = \omega_{2n}^{2k}\)。
数论中,原根恰好满足这些性质。
对于一个素数的原根 \(g\),设 \(g_n = g ^ {\frac{p - 1}{n}}\)。那么:
\]
\]
\]
\]
我们发现它满足 \(\omega_{n}^{k}\) 的全部性质!
因此,只需要用 \(g_{n}^k = g_{}^{\frac{p - 1}{n} k}\) 带替全部的 \(\omega_{n}^{k}\) 就可以了。
tips:对于一个质数,只有当它可以表示成 \(p = 2 ^ \alpha + 1\),且需要满足多项式的项数 \(< \alpha\) 时才能使用 NTT。\(p\) 后面有个加一,是因为 \(g_n\) 指数的分子上出现了 \(-1\);\(p - 1\) 需要时二的整数次幂,是因为每次都要除以 \(2\)。
bonus:常用质数及原根
\(p = 998244353 = 119 \times 2 ^ {23} + 1, g = 3\)
\(p = 1004535809 = 479 \times 2 ^ {21} + 1, g = 3\)
void NTT(int *a, int n, int op) {
if (n == 1) return;
int a1[n], a2[n];
rop(i, 0, n >> 1) a1[i] = a[i << 1], a2[i] = a[(i << 1) + 1];
NTT(a1, n >> 1, op), NTT(a2, n >> 1, op);
int gn = qpow((op == 1 ? g : invg), (mod - 1) / n), gk = 1;
rop(i, 0, n >> 1) {
a[i] = (a1[i] + 1ll * gk * a2[i]) % mod;
a[i + (n >> 1)] = (a1[i] - 1ll * gk * a2[i] % mod + mod) % mod;
gk = 1ll * gk * gn % mod;
}
}
NTT 优化:蝴蝶变换
盗大佬一张图
这是进行 NTT 的过程中数组下标的变化。
这样看似乎毫无规律。但是把他们写成二进制:
变换前:\(000 ~ 001 ~ 010 ~ 011 ~ 100 ~ 101 ~ 110 ~ 111\)
变换后:\(000 ~ 100 ~ 010 ~ 110 ~ 001 ~ 101 ~ 011 ~ 111\)
可以发现,就是对每个下标进行了二进制翻转。
因此可以先预处理出每个下标在递归底层对应的新下标。然后从底层往顶层迭代合并。
预处理每个下标的二进制翻转:
rop(i, 0, n) rev[i] = rev[i >> 1] >> 1 | (i & 1) << bit;
优化后的 NTT:
void NTT(int *a, int n, int op) {
rop(i, 0, n) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int mid = 1; (mid << 1) <= n; mid <<= 1) {
int gn = qpow((op == 1 ? g : invg), (mod - 1) / (mid << 1));
for (int i = 0, gk = 1; i < n; i += (mid << 1), gk = 1)
for (int j = 0; j < mid; j ++ , gk = 1ll * gk * gn % mod) {
int x = a[i + j], y = 1ll * a[i + j + mid] * gk % mod;
a[i + j] = Mod(x + y), a[i + j + mid] = Mod(x - y);
}
}
}
当然了,FFT 也可以用蝴蝶变换来优化。实践证明,速度变快了至少二分之一。
FFT 的迭代实现
void FFT(cp *a, int n, int op) {
rop(i, 0, n) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int mid = 1; (mid << 1) <= n; mid <<= 1) {
cp Wn = {cos(pi / mid), op * sin(pi / mid)};
for (int i = 0; i < n; i += (mid << 1)) {
cp Wk = {1, 0};
for (int j = 0; j < mid; j ++ , Wk = Wk * Wn) {
cp x = a[i + j], y = Wk * a[i + j + mid];
a[i + j] = x + y, a[i + j + mid] = x - y;
}
}
}
}
任意模数多项式乘法(MTT)
计算 \(f \times g(\bmod ~ p)\) 的结果(\(p\) 不一定能够表示成 \(2 ^ \alpha + 1\) 的形式)。
这个东西有两种做法,但是我只学会了三模 NTT。
首先,卷积之后每个系数最多达到 \(\max \{V\} ^ 2 \times n\) 的大小。拿模板题举例,这个东西就是 \(10 ^ {23}\)。因此只需要找三个模数 \(p_1, p_2, p_3\),满足 \(p_1p_2p_3 > \max \{V\} ^ 2 \times n\),然后用他们分别 NTT,最后再 CRT / exCRT 合并即可。
int CRT(int a, int b, int c, int p) {
int k = 1ll * Mod(b - a, p2) * inv1 % p2;
LL x = 1ll * k * p1 + a;
k = 1ll * Mod((c - x) % p3, p3) * inv2 % p3;
x = (x + 1ll * k * p1 % p * p2 % p) % p;
return x;
}
void MTT(int *a, int n, int *b, int m, int p) {
int B = ((n + m) << 2) + 1;
rev = new int [B]();
int *a1 = new int [B](), *b1 = new int [B]();
int *a2 = new int [B](), *b2 = new int [B]();
int *a3 = new int [B](), *b3 = new int [B]();
rop(i, 0, B)
a1[i] = a2[i] = a3[i] = a[i], b1[i] = b2[i] = b3[i] = b[i];
NTT(a1, n, b1, m, p1); NTT(a2, n, b2, m, p2); NTT(a3, n, b3, m, p3);
inv1 = inv(p1, p2); inv2 = inv(1ll * p1 * p2 % p3, p3);
rep(i, 0, n + m) a[i] = CRT(a1[i], a2[i], a3[i], p);
}
这里选择的模数为:\(p_1 = 998244353, p_2 = 469762049, p_3 = 1004535809\)。他们的原根都为 \(g = 3\)。
多项式求逆
求多项式 \(f(x)\) 的逆元 \(f^{-1}(x)\)。\(f(x)\) 的逆元定义为满足 \(f(x) g(x) = 1(\bmod \ x ^ n)\) 的多项式 \(g(x)\)。
使用倍增法即可求出多项式的逆元。
首先假设已经求出了 \(f(x) g'(x) \equiv 1(\bmod \ x ^ {\lceil \frac{n}{2} \rceil})\)。再假设 \(f(x) \bmod \ x ^ n\) 意义下逆元为 \(g(x)\)。那么有:
\]
\]
\]
\]
两边同时乘以 \(f(x)\),可以得到:
\]
\]
\]
倍增求即可。
void Inv(int *f, int *g, int n) {
if (n == 1) {
g[0] = inv(f[0]); return;
} Inv(f, g, (n + 1) >> 1);
int m = 1, bit = 0;
while (m < (n << 1)) m <<= 1, bit ++ ; bit -- ;
rop(i, 0, n) tmp[i] = f[i];
rop(i, n, m) tmp[i] = 0;
rev = new int [m + 5]();
rop(i, 1, m) rev[i] = (rev[i >> 1] >> 1) | (i & 1) << bit;
NTT(tmp, m, 1); NTT(g, m, 1);
rop(i, 0, m) g[i] = (2ll - 1ll * g[i] * tmp[i] % p + p) % p * g[i] % p;
NTT(g, m, -1); int invn = inv(m);
rop(i, 0, m) g[i] = 1ll * g[i] * invn % p;
rop(i, n, m) g[i] = 0;
free(rev); rev = NULL;
}
分治 FFT
给定序列 \(g_{1\dots n - 1}\),求序列 \(f_{0\dots n - 1}\)。
其中 \(f_i=\sum_{j=1}^if_{i-j}g_j\),边界为 \(f_0=1\)。
答案对 \(998244353\) 取模。
其实这是个多项式求逆板子
首先考虑生成函数 \(F(x) = \sum _{i = 0}^{+ \infty} f_i x ^ i, G(x) = \sum _{i = 0}^{ + \infty}g_i x ^ i\)。然后可以发现:
\]
因此 \(F(x)(G(x) - 1) = -f_0\),也就是:
\]
所以直接设 \(g_0 = 0\),然后把 \(1 - g\) 求逆就行了。
read(n);
rop(i, 1, n) read(g[i]);
rop(i, 1, n) g[i] = Poly::p - g[i];
(g[0] += 1) %= Poly::p; Poly::Inv(g, n);
rop(i, 0, n) write(' ', g[i]); return 0;
多项式对数函数(Polyln)
给定 \(f(x)\),求多项式 \(g(x)\) 使得 \(g(x) \equiv \ln(f(x)) (\bmod \ x ^ n)\)
前置知识:简单的求导和积分,以及基本的多项式模板。
首先设 \(h(x) = \ln(x)\),那么
\]
然后对同余方程两边同时求导,得到
\]
根据复合函数求导公式 \(f'(g(x)) = f'(g(x))g'(x)\) 可得,
\]
然后又有 \(\ln '(x) = \dfrac{1}{x}\),因此
\]
计算 \(f'(x)\) 和 \(f^{-1}(x)\) 作为 \(a, b\)。计算 \(a \times b (\bmod \ 998244353)\) 得到 \(g'(x)\) ,然后求 \(g'(x)\) 的积分就好了。
积分公式:\(\int x ^ a \mathrm{dx} = \dfrac{1}{a + 1} x ^ {a + 1}\)。
void der(int *f, int n) { rep(i, 1, n) f[i - 1] = 1ll * i * f[i] % p; f[n] = 0; }
void Int(int *f, int n) {dep(i, n, 1) f[i] = 1ll * inv(i) * f[i - 1] % p; f[0] = 0; }
void Ln(int *f, int n) {
int B = (n << 2) + 5; int *_f = new int[B]();
rep(i, 0, n) _f[i] = f[i];
der(_f, n), Inv(f, n);
NTT(f, n, _f, n); Int(f, n);
free(_f); _f = NULL;
}
多项式指数函数(PolyExp)
给定多项式 \(f(x)\),求 \(g(x)\) 满足 \(g(x) \equiv e ^ {f(x)} (\bmod \ x ^ n)\)。
前置知识:牛顿迭代。
牛顿迭代是用来求函数零点的有力工具。
例如,下面这个图是使用牛顿迭代法求 \(f(x)=x^4+3 x^2+7 x+3\) 零点的过程。
首先,随便选择一个点 \((x_0, f(x_0))\),过这个点做 \(f(x)\) 的切线。切线方程是 \(y = f'(x_0)(x - x_0) + f(x_0)\)。它与 \(x\) 轴交于一点 \(A(-0.56243, 0)\)。
接下来,再令 \(x_1 = -0.56243\),过点 \((x_1, f(x_1))\) 再做 \(f(x)\) 的切线,与 \(x\) 轴交于点 \(B(-0.60088, 0)\)。
再令 \(x_2 = -0.60088\),如此迭代下去。可以发现,\(x_n\) 会逐渐逼近零点。
刚才说切线方程为 \(y = f'(x_0)(x - x_0) + f(x_0)\)。如果令 \(y = 0\),得到的 \(x\) 便是切线方程与 \(x\) 轴的交点,为
\]
运用于多项式,假设要求使 \(f(g(x)) \equiv 0(\bmod \ x ^ n)\) 的多项式 \(g(x)\),并且已经知道 \(f(g_0(x)) \equiv 0 (\bmod \ x ^ {\frac{n}{2}})\)。
那么可以说,
\]
接下来解决多项式 Exp。所求为 \(g(x)\) 使得 \(g(x) \equiv e ^ {f(x)} (\bmod \ x ^ n)\)。两边都取 \(\ln\) 得到:
\]
移项得:
\]
设 \(F(g(x)) = ln g(x) - f(x)\),那么所求就是 \(F(x)\) 的零点。
假设已经有 \(g_0(x)\) 使得 \(F(g_0(x)) \equiv 0 (\bmod \ x ^ {\frac{n}{2}})\),根据上面的牛顿迭代,有:
\]
根据这个柿子倍增求即可。每次需要计算一个 \(\ln\),一个乘法,时间 \(O(n \log n)\)。
写的有点丑,超级大肠数。
void Exp(int *f, int *g, int n) {
if (n == 1) return void(g[0] = 1);
Exp(f, g, (n + 1) >> 1);
int B = (n << 2) + 5; int *lnb = new int[B]();
rop(i, 0, n) lnb[i] = g[i]; Ln(lnb, n);
tmp = new int[B](); rop(i, 0, n) tmp[i] = f[i];
rop(i, 0, n) tmp[i] = (1ll * tmp[i] - lnb[i] + p) % p;
tmp[0] ++ ; NTT(g, n, tmp, n);
free(tmp); tmp = NULL; free(lnb); lnb = NULL;
}
多项式快速幂(PolyPower)
在模 \(x ^ n\) 意义下计算 \(f^k(x)\)。
有了前面的知识铺垫,这部分就非常的简单。
根据对数恒等式,有 \(f(x) = e ^ {\ln f(x)}\)。
因此 \(f^k(x) = e ^ {k \ln f(x)}\)。
把 \(f(x)\) 乘以 \(k\),求多项式 \(\ln\),然后再 exp 回来就行了。
void Power(int *f, int n, int k) {
Ln(f, n); rop(i, 0, n) f[i] = 1ll * f[i] * k % p; Exp(f, n);
}
多项式开根
在模 \(x ^ n\) 意义下计算 \(f^{\frac{1}{k}}(x)\)。
这个最简单。直接把 \(\frac{1}{k} \bmod p\),然后多项式快速幂。
多项式(Poly)笔记的更多相关文章
- FFT/NTT 多项式学习笔记
FFT(快速傅立叶变换)和NTT(快速数论变换)看上去很高端,真正搞懂了就很simple了辣. 首先给出多项式的一些定义(初中数学内容): 形如Σaixi的式子就是多项式! 多项式中每个单项式叫做多项 ...
- [FML]学习笔记二 PAC Learning Model
对于一个concept class C,如果存在一个算法A和一个多项式poly(.,.,.,.),有对于任意的ε>0.δ>0以及X的任意分布D和任何target concept C,当sa ...
- MATLAB多项式运算
序言 none 正文 1. 多项式的表示 在Matlab中,多项式用一个行向量表示, 行向量的元素值为多项式系数按幂次的降序排列, 如p(x)=x3-2x-5用P=[1,0,-2,-5]表示. 2. ...
- OO面向对象第一单元总结
OO面向对象第一单元总结(表达式求导) 写在前面: 魔鬼课程oo第一单元终于结束,当终究要落笔总结,竟不知从何写起…… 回首再去看第一次的作业,你会满足于那时的幸福,或许,这就是成长吧! 千言万语,一 ...
- OO第一单元三次作业总结
写在前面 第一单元作业是针对输入的多项式进行格式合法判断,然后进行求导,结果长度优化,最后输出.三次难度递增,不断添加新的需求,总体感觉在实现方面没有多大困难(?),个人主要困扰环节是寻找自己未知bu ...
- OO最后一次作业
终于开始最后一次作业了,是时候为这学期oo画一个圆满的局句号了. 回首这学期的OO经历,一路走来,经过了开始对面向对象的初步接触,然后就是充满痛苦回忆的多线程,接下来到了令人焦头烂额的规格设计,最后是 ...
- 使用支持向量机(SVM) 算法进行分类
1 支持向量机(SVM)的基本概念 SVM是一种分类算法,其侧重于模式识别方面.使用SVM可以大大提高分类的准确性. 分类相当于模式识别的子集合,模式识别重点在于对已知数据进行特征发现与提取. ...
- sklearn5_preprocessing数据标准化
sklearn实战-乳腺癌细胞数据挖掘(博主亲自录制视频) https://study.163.com/course/introduction.htm?courseId=1005269003& ...
- sklearn.svm.SVC 参数说明
原文地址:sklearn.svm.SVC 参数说明 ============================== 资源: sklearn官网+DOC 库下载GitHub =============== ...
- python中库学习
一.numpy NumPy的主要对象是同种元素的多维数组.这是一个所有的元素都是一种类型.通过一个正整数元组索引的元素表格(通常是元素是数字).在NumPy中维度(dimensions)叫做轴(axe ...
随机推荐
- SpringCloud-ZipKin搭建保姆级教程
服务链路追踪 一.服务追踪说明 微服务架构是通过业务来划分服务的,使⽤REST调⽤.对外暴露的⼀个接⼝,可能需要 很多个服务协同才能完成这个接⼝功能,如果链路上任何⼀个服务出现问题或者⽹络超 时,都会 ...
- 搞懂fflush(stdout)
使用 printf 或 cout 打印内容时,输出永远不会直接写入"屏幕".而是,被发送到 stdout. (stdout 就像一个缓冲区) 默认情况下,发送到 stdout 的输 ...
- android 中service的简单事例
源码 public class ServiceDemoActivity extends Activity { private static final String TAG = "Servi ...
- isHex
public class Test { public static boolean isHex(String str) { boolean isHexFlg = true; int i = 0; ch ...
- SaaS模式相较传统CRM系统有何优势?
SaaS模式的CRM客户管理系统相较于传统的CRM客户管理系统更加方便灵活,更加符合如今的市场环境.它解决了传统CRM系统投入大.维护难的难题,降低了中小企业导入CRM的门槛.下面详细说说SaaS模式 ...
- go使用snmp库查询mib数据
转载请注明出处: OID(Object Identifier)是一种用于标识和唯一命名管理信息库中的对象的标准方式.给定一个OID,可以确定特定的管理信息库对象,并对其进行操作. go语言使用snmp ...
- js性能优化解决办法
1. 减少http请求次数:CSS Sprites, JS.CSS 源码压缩.图片大小控制合适:网页 Gzip,CDN 托管,data 缓存 ,图片服务器 2. 前端模板 JS + 数据,减少由于HT ...
- Unity - EditorWindow 折叠树显示(IMGUI)
仅适用于2018之前的版本,有UIElements或者UIWidgets的最好用新的 基本实现 树节点 public interface ITreeNode { ITreeNode Parent { ...
- .NET中的数组在内存中如何布局?
总的来说,.NET的值类型和引用类型都映射一段连续的内存片段.不过对于值类型对象来说,这段内存只需要存储其字段成员,而对应引用类型对象,还需要存储额外的内容.就内存布局来说,引用类型有两个独特的存在, ...
- 一个基于ASP.NET Core完全开源的CMS 解决方案
本文简介 MixCoreCMS是一个基于.NET Core框架的开源内容管理系统(CMS),提供了丰富的的基础功能和插件,是一款面向未来的企业 Web CMS,可轻松构建任何类型的应用程序.集成了Go ...