多项式 学习笔记
本文目录(18 节)
唉,只能功利的学学了......
多项式
长成
这个鬼样子的式子就叫做多项式。
一般要求 ,在这种情况下, 被称为多项式 的度,记作 。
拉格朗日插值
【模板】P4781 【模板】拉格朗日插值
由小学知识可知个点 可以唯一地确定一个多项式 。现在,给定这 个点,请你确定这个多项式,并求出 。
首先,有 。
构造 个多项式 ,其中 。
在这种构造下,,。
因此,有 ,即: 那么, 显然可以 求了。
const int N = 2e3 + 7;
int n;
modint k, x[N], y[N], ans;
int main() {
rd(n), rd(k);
for (int i = 1; i <= n; i++) rd(x[i]), rd(y[i]);
for (int i = 1; i <= n; i++) {
modint a = y[i], b = 1;
for (int j = 1; j <= n; j++)
if (i != j) a *= k - x[j], b *= x[i] - x[j];
ans += a / b;
}
print(ans);
return 0;
}
注意,当 或者 时,进一步化简公式可以做到 求 ,但是要求出多项式还是得 。
多项式乘法
快速傅里叶变换 (FFT)
利用复数将 的多项式乘法优化到 。
【模板】P3803 【模板】多项式乘法
namespace FFT {
const int N = 1 << 21 | 1;
const double PI = acos(-1);
struct I {
double x, y;
inline I() {}
inline I(double x, double y) : x(x), y(y) {}
inline I &operator = (int o) { return x = o, y = 0, *this; }
inline I operator + (const I o) const { return I(x + o.x, y + o.y); }
inline I operator - (const I o) const { return I(x - o.x, y - o.y); }
inline I operator * (const I o) const {
return I(x * o.x - y * o.y, x * o.y + y * o.x);
}
} a[N], b[N];
inline void rd(I &x) { int o; ::rd(o), x = o; }
inline void print(I x, char k = '\n') { ::print((int)(x.x + 0.5), k); }
int n, m, k, l, r[N];
inline void fft(I *a, int n, int x) {
for (int i = 0; i < n; i++) if (i < r[i]) swap(a[i], a[r[i]]);
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1) {
I W = I(cos(PI / k), x * sin(PI / k));
for (int i = 0; i < n; i += o) {
I w = I(1, 0);
for (int j = 0; j < k; j++, w = w * W) {
I x = a[i+j], y = w * a[i+j+k];
a[i+j] = x + y, a[i+j+k] = x - y;
}
}
}
}
inline void solve() {
k = 1, l = 0;
while (k <= n + m) k <<= 1, ++l;
for (int i = 0; i < k; i++)
r[i] = (r[i>>1] >> 1) | ((i & 1) << (l - 1));
for (int i = n + 1; i < k; i++) a[i] = 0;
for (int i = m + 1; i < k; i++) b[i] = 0;
fft(a, k, 1), fft(b, k, 1);
for (int i = 0; i < k; i++) a[i] = a[i] * b[i];
fft(a, k, -1);
for (int i = 0; i <= n + m; i++) a[i].x /= k;
}
}
using namespace FFT;
int main() {
rd(n), rd(m);
for (int i = 0; i <= n; i++) rd(a[i]);
for (int i = 0; i <= m; i++) rd(b[i]);
solve();
for (int i = 0; i <= n + m; i++) print(a[i], " \n"[i==n+m]);
return 0;
}
快速数论变换 (NTT)
利用原根将 的多项式乘法优化到 。
【模板】P3803 【模板】多项式乘法
namespace NTT {
const int N = 1 << 21 | 1;
int n, m, k, l, r[N];
modint vk, g = 3, vg = 332748118, a[N], b[N];
inline void ntt(modint *a, int n, int x) {
for (int i = 0; i < n; i++) if (i < r[i]) swap(a[i], a[r[i]]);
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1) {
modint W = (x > 0 ? g : vg) ^ ((P - 1) / o);
for (int i = 0; i < n; i += o) {
modint w = 1;
for (int j = 0; j < k; j++, w *= W)
a[i+j+k] *= w,
a[i+j] += a[i+j+k],
a[i+j+k] = a[i+j] - a[i+j+k] - a[i+j+k];
}
}
}
inline void solve() {
k = 1, l = 0;
while (k <= n + m) k <<= 1, ++l;
vk = (modint)1 / k;
for (int i = 0; i < k; i++)
r[i] = (r[i>>1] >> 1) | ((i & 1) << (l - 1));
for (int i = n + 1; i < k; i++) a[i] = 0;
for (int i = m + 1; i < k; i++) b[i] = 0;
ntt(a, k, 1), ntt(b, k, 1);
for (int i = 0; i < k; i++) a[i] *= b[i];
ntt(a, k, -1);
for (int i = 0; i <= n + m; i++) a[i] *= vk;
}
}
using namespace NTT;
int main() {
rd(n), rd(m);
for (int i = 0; i <= n; i++) rd(a[i]);
for (int i = 0; i <= m; i++) rd(b[i]);
solve();
for (int i = 0; i <= n + m; i++) print(a[i], " \n"[i==n+m]);
return 0;
}
多项式位运算卷积 (FWT)
记对序列 进行快速沃尔什变换后的序列为 。
已知序列 ,求一个新序列 ,直接计算是 的。
若 和 是 的,而 是 的,同时 也是 的。
那么我们可以利用上述过程 求出 。
在 OI 中,FWT 是用于解决对下标进行位运算卷积问题的方法。
其中 是二元位运算中的一种。
或
要求
显然有 。
构造 。
则有
要求 令 表示 中下标最高位为 的那部分序列, 表示 中下标最高位为 的那部分序列。
则有 其中 表示「拼接」, 表示对应位置相加。
于是可以分治。
inline void OR(modint *f) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j+k] += f[i+j];
}
由 可得
inline void IOR(modint *f) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j+k] -= f[i+j];
}
显然两份代码可以合并。
inline void OR(modint *f, modint x = 1) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j+k] += f[i+j] * x;
}
与
同理或。
inline void AND(modint *f, modint x = 1) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j] += f[i+j+k] * x;
}
异或
定义 ,其中 表示「二进制下 的个数」。
满足 。
构造 。
则有
因此
inline void XOR(modint *f, modint x = 1) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j] += f[i+j+k],
f[i+j+k] = f[i+j] - f[i+j+k] - f[i+j+k],
f[i+j] *= x, f[i+j+k] *= x;
}
【模板】P4717 【模板】快速沃尔什变换 (FWT)
const int N = 1 << 17 | 1;
int n, m;
modint A[N], B[N], a[N], b[N];
inline void in() {
for (int i = 0; i < n; i++) a[i] = A[i], b[i] = B[i];
}
inline void get() {
for (int i = 0; i < n; i++) a[i] *= b[i];
}
inline void out() {
for (int i = 0; i < n; i++) print(a[i], " \n"[i==n-1]);
}
inline void OR(modint *f, modint x = 1) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j+k] += f[i+j] * x;
}
inline void AND(modint *f, modint x = 1) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j] += f[i+j+k] * x;
}
inline void XOR(modint *f, modint x = 1) {
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1)
for (int i = 0; i < n; i += o)
for (int j = 0; j < k; j++)
f[i+j] += f[i+j+k],
f[i+j+k] = f[i+j] - f[i+j+k] - f[i+j+k],
f[i+j] *= x, f[i+j+k] *= x;
}
int main() {
rd(m), n = 1 << m;
for (int i = 0; i < n; i++) rd(A[i]);
for (int i = 0; i < n; i++) rd(B[i]);
in(), OR(a), OR(b), get(), OR(a, P - 1), out();
in(), AND(a), AND(b), get(), AND(a, P - 1), out();
in(), XOR(a), XOR(b), get(), XOR(a, (modint)1 / 2), out();
return 0;
}
分治 FFT
分治 FFT 并不局限于 FFT,是将 CDQ 分治和多项式乘法 (FFT/NTT) 结合起来的思想。
【模板】P4721 【模板】分治 FFT
给定序列 ,求序列 。
其中 ,边界为 。
答案对 取模。
稍微变换一下,有 。
注意到 是依赖于 的,因此如果依次计算每一项,时间复杂度无法承受。
考虑 CDQ 分治,即对于 的值,我们先直接计算 的值,再考虑 对 的贡献,最后计算 的值。
现在的问题变成如何计算 对 的贡献。
考虑对于 , 对 的贡献为: 设 ,则转化为: 发现这是个卷积式,设 ,可以 计算。
总时间复杂度 。
const int N = 1e5 + 7;
int n;
modint f[N], g[N];
namespace NTT {
const int N = 1 << 21 | 1;
int n, m, k, l, r[N];
modint vk, g = 3, vg = 332748118, a[N], b[N];
inline void ntt(modint *a, int n, int x) {
for (int i = 0; i < n; i++) if (i < r[i]) swap(a[i], a[r[i]]);
for (int o = 2, k = 1; o <= n; o <<= 1, k <<= 1) {
modint W = (x > 0 ? g : vg) ^ ((P - 1) / o);
for (int i = 0; i < n; i += o) {
modint w = 1;
for (int j = 0; j < k; j++, w *= W)
a[i+j+k] *= w,
a[i+j] += a[i+j+k],
a[i+j+k] = a[i+j] - a[i+j+k] - a[i+j+k];
}
}
}
inline void solve() {
k = 1, l = 0;
while (k <= n + m) k <<= 1, ++l;
vk = (modint)1 / k;
for (int i = 0; i < k; i++)
r[i] = (r[i>>1] >> 1) | ((i & 1) << (l - 1));
for (int i = n + 1; i < k; i++) a[i] = 0;
for (int i = m + 1; i < k; i++) b[i] = 0;
ntt(a, k, 1), ntt(b, k, 1);
for (int i = 0; i < k; i++) a[i] *= b[i];
ntt(a, k, -1);
for (int i = 0; i <= n + m; i++) a[i] *= vk;
}
}
void cdq(int l, int r) {
if (l == r) return;
int mid = (l + r) >> 1;
cdq(l, mid);
NTT::n = mid - l, NTT::m = r - l - 1;
for (int i = 0; i <= mid - l; i++) NTT::a[i] = f[i+l];
for (int i = 0; i < r - l; i++) NTT::b[i] = g[i+1];
NTT::solve();
for (int i = mid + 1; i <= r; i++) f[i] += NTT::a[i-l-1];
cdq(mid + 1, r);
}
int main() {
rd(n), f[0] = 1;
for (int i = 1; i < n; i++) rd(g[i]);
cdq(0, n - 1);
for (int i = 0; i < n; i++) print(f[i], " \n"[i==n-1]);
return 0;
}
参考资料
- OI Wiki 快速沃尔什变换
- wjyyy 快速沃尔什变换 FWT 学习笔记【多项式】
- Igronemyk 分治 FFT 学习笔记
评论
暂无已公开评论。