lswzk
除法、开根、exp、多点插值、快速求值、拉格朗日插值的板子还没有整理,重心拉格朗日插值法还不会,正在补坑中。
\color{red}{\text{约定:}}
多项式的表示:
多项式可以通过系数数列
多项式可以通过点值表示,对于一个
多项式乘法:
定义
复数:
复数一般情况下可以表示成
复数的幅角:平面直角坐标系上点
复数的模长:
两个复数相乘:
复数可以加减乘除,可以和实数一样的带入
单位根:
在单位圆上从
画图观察可得:
DFT&IDFT:
在科学的数学函数意义上DFT是讲一个函数转化成三角函数的加减乘除的形式,三角函数的系数是原函数系数与点值之间的变换规律。IDFT是DFT的逆变换。
原根:
(NTT用)
由
带入二式,得
若
最后
泰勒展开:
(牛顿迭代用)
简单来说,
牛顿迭代:
(开根、exp用)
牛顿迭代是用来求解零点问题的一种方法。
已知
拉格朗日插值法:
(多项式快速插值用)
这样,
复杂度
修改时,我们只需要
\color{red}{\text{多项式全集之一 FFT:}}
什么是FFT:
FFT是利用DFT的特殊性质,把
w 的具体应用:
综上所述,对于点值取的
我们令
我们可以发现
现在我们把
我们知道取
非递归优化FFT:
而在IDFT时,我们需要
数论优化FFT(NTT):
因为有这些共性,所以
喜闻乐见的模板:
FFT模板(共轭优化)
namespace FFT{
const double pi = acos(-1);
struct cp{
double x, y;
cp() {x = y = 0;}
cp(double X,double Y) {x = X; y = Y; }
cp conj() {return (cp) {x, -y};}
}a[3000005], b[3000005], c[3000005], I(0, 1), d[3000005];
cp operator+ (const cp &a, const cp &b) {return (cp){a.x + b.x, a.y + b.y}; }
cp operator- (const cp &a, const cp &b) {return (cp){a.x - b.x, a.y - b.y}; }
cp operator* (const cp &a, const cp &b) {return (cp){a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x};}
cp operator* (const cp &a, double b) {return (cp){a.x * b, a.y * b};}
cp operator/ (const cp &a, double b) {return (cp){a.x / b, a.y / b};}
struct p_l_e{
int wz[3000005];
void FFT(cp *a, int N, int op) {
for(int i = 0; i < N; i++)
if (i<wz[i]) swap(a[i],a[wz[i]]);
for(int le = 2; le <= N; le <<= 1) {
int mid = le >> 1;
for(int i = 0; i < N; i += le) {
cp x, y, w = (cp) {1, 0};
cp wn = (cp){cos(op * pi / mid), sin(op * pi / mid)};
for(int j = 0 ; j < mid; j++) {
x = a[i + j]; y = a[i + j + mid] * w;
a[i + j] = x + y;
a[i + j + mid] = x - y;
w = w * wn;
}
}
}
}
void D_FFT(cp *a, cp *b, int N, int op){
for(int i = 0; i < N; i++) d[i] = a[i] + I * b[i];
FFT(d, N, op);
d[N] = d[0];
for(int i = 0; i < N; i++){
a[i] = (d[i] + d[N - i].conj()) / 2;
b[i] = I * (-1) * (d[i] - d[N - i].conj()) / 2;
}
d[N] = cp(0, 0);
}
void mult(cp *a, cp *b, cp *c, int M){
int N = 1, len = 0;
while(N < M) N <<= 1, len++;
for(int i = 0; i < N; i++)
wz[i] = (wz[i >> 1] >> 1) | ((i & 1) << (len - 1));
D_FFT(a, b, N, 1);
for(int i = 0; i < N; i++) c[i] = a[i] * b[i];
FFT(c, N, -1);
for(int i = 0; i < N; i++) c[i].x = c[i].x / N;
}
}PLE;
int n, m;
void main() {
scanf("%d%d", &n, &m); n++; m++;
for(int i = 0; i < n; i++) scanf("%lf", &a[i].x);
for(int i = 0; i < m; i++) scanf("%lf", &b[i].x);
PLE.mult(a, b, c, n + m - 1);
for(int i = 0; i < n + m - 1; i++) printf("%d ", (int)round(c[i].x));
return;
}
}
NTT模板:
namespace NTT{
typedef long long LL;
const int mod = 998244353;
const int g = 3;
LL a[3000005], b[3000005], c[3000005];
int n, m;
LL qpow(LL a, LL b){
LL ans = 1;
while(b){
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
struct p_l_e{
int wz[3000005];
void NTT(LL *a, int N, int op) {
for(int i = 0; i < N; i++)
if(i < wz[i]) swap(a[i], a[wz[i]]);
for(int le = 2; le <= N; le <<= 1) {
int mid = le >> 1;
LL wn = qpow(g, (mod - 1) / le);
if(op == -1) wn = qpow(wn, mod - 2);
for(int i = 0; i < N; i += le) {
int w = 1, x, y;
for(int j = 0; j < mid; j++) {
x = a[i + j];
y = a[i + j + mid] * w % mod;
a[i + j] = (x + y) % mod;
a[i + j + mid] = (x - y + mod) % mod;
w = w * wn % mod;
}
}
}
}
void mult(LL *a, LL *b, LL *c, int M) {
int N = 1, len = 0;
while(N < M) N <<= 1, len++;
for(int i = 0; i < N; i++)
wz[i] = (wz[i >> 1] >> 1) | ((i & 1) << (len - 1));
NTT(a, N, 1); NTT(b, N, 1);
for(int i = 0; i < N; i++) c[i] = a[i] * b[i] % mod;
NTT(c, N, -1);
LL t = qpow(N, mod - 2);
for(int i = 0; i < N; i++) c[i] = c[i] * t % mod;
}
}PLE;
void main() {
scanf("%d%d", &n, &m); n++; m++;
for(int i = 0; i < n; i++) scanf("%lld", &a[i]);
for(int i = 0; i < m; i++) scanf("%lld", &b[i]);
PLE.mult(a, b, c, n + m - 1);
for(int i = 0; i < n + m - 1; i++) printf("%lld ", c[i]);
}
}
\color{red}{\text{多项式全集之二 任长任模的FFT:}}
三模NTT现任意模数FFT(MTT):
:-:|:-:|:-:
拆系数FFT(CFFT)实现任模FFT:
喜闻乐见的模板:
三模NTT模板(注意:不可以MTT回来,因为系数会取模)
namespace MTT{
typedef long long LL;
int n, m;
LL p, mod;
const LL p1 = 998244353;
const LL p2 = 1004535809;
const LL p3 = 104857601;
const int g = 3189;
LL a[300005], b[300005], c[300005], cpa[300005], cpb[300005];
LL c3[300005], c1[300005], c2[300005];
LL qpow(LL a, LL b, LL mod) {
LL ans = 1;
while(b) {
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
const LL inv12 = qpow(p1, p2 - 2, p2);
const LL inv123 = qpow(p1 * p2 % p3, p3 - 2, p3);
struct p_l_e{
int wz[300005];
void MTT(LL *a, int N, int op) {
for(int i = 0; i < N; i++)
if(i < wz[i]) swap(a[i], a[wz[i]]);
for(int le = 2; le <= N; le <<= 1) {
int mid = le >> 1;
LL wn = qpow(g, (mod - 1) / le, mod);
if(op == -1) wn = qpow(wn, mod - 2, mod);
for(int i = 0; i < N ;i += le) {
LL w = 1, x, y;
for(int j = 0; j < mid; j++) {
x = a[i + j];
y = a[i + j + mid] * w % mod;
a[i + j] = (x + y) % mod;
a[i + j + mid] = (x - y + mod) % mod;
w = w * wn % mod;
}
}
}
}
void mult(LL *a, LL *b, LL *c, int M) {
int N = 1, len = 0;
while(N < M) N <<= 1, len++;
for(int i = 0; i < N; i++)
wz[i] = (wz[i >> 1] >> 1) | ((i & 1) << (len - 1));
MTT(a, N, 1); MTT(b, N, 1);
for(int i = 0; i < N; i++) c[i] = a[i] * b[i] % mod;
MTT(c, N, -1);
LL t = qpow(N, mod - 2, mod);
for(int i = 0; i < N; i++) c[i] = c[i] * t % mod;
}
}PLE;
LL CRT(LL c1, LL c2, LL c3) {
LL x = (c1 + p1 * ((c2 - c1 + p2) % p2 * inv12 % p2));
LL y = (x % p + p1 * p2 % p * ((c3 - x % p3 + p3) % p3 * inv123 % p3) % p) % p;
return y;
}
void merge(LL *c1, LL *c2, LL *c3, LL *c, int N) {
for(int i = 0; i < N; i++)
c[i] = CRT(c1[i], c2[i], c3[i]);
return;
}
void main() {
scanf("%d%d%lld", &n, &m, &p); n++; m++;
for(int i = 0; i < n; i++) scanf("%lld", &a[i]);
for(int i = 0; i < m; i++) scanf("%lld", &b[i]);
mod = p1; memcpy(cpa, a, sizeof(a)); memcpy(cpb, b, sizeof(b)); PLE.mult(cpa, cpb, c1, n + m - 1);
mod = p2; memcpy(cpa, a, sizeof(a)); memcpy(cpb, b, sizeof(b)); PLE.mult(cpa, cpb, c2, n + m - 1);
mod = p3; memcpy(cpa, a, sizeof(a)); memcpy(cpb, b, sizeof(b)); PLE.mult(cpa, cpb, c3, n + m - 1);
merge(c1, c2, c3, c, n + m - 1);
for(int i = 0; i < n + m - 1; i++) printf("%lld ", (c[i] % p + p) % p);
return;
}
}
拆系数FFT模板(注意:相同系数的两项可以合并一起IDFT。采用共轭优化法,只进行四次DFT)
namespace CFFT{
typedef long long LL;
int n, m, p ,sqrp;
int a[300005], b[300005];
const long double pi = acos(-1);
struct cp{
long double x, y;
cp() {x = y = 0;}
cp(long double X,long double Y) {x = X; y = Y; }
cp conj() {return (cp) {x, -y};}
}ka[300005], kb[300005], ta[300005], tb[300005], kk[300005], kt[300005], tt[300005], c[300005], I(0, 1), d[300005];
cp operator+ (const cp &a, const cp &b) {return (cp){a.x + b.x, a.y + b.y}; }
cp operator- (const cp &a, const cp &b) {return (cp){a.x - b.x, a.y - b.y}; }
cp operator* (const cp &a, const cp &b) {return (cp){a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x};}
cp operator* (const cp &a, long double b) {return (cp){a.x * b, a.y * b};}
cp operator/ (const cp &a, long double b) {return (cp){a.x / b, a.y / b};}
struct p_l_e{
int wz[300005];
void FFT(cp *a, int N, int op){
for(int i = 0; i < N; i++)
if(i < wz[i]) swap(a[i], a[wz[i]]);
for(int le = 2; le <= N; le <<= 1){
int mid = le >> 1;
cp x, y, w, wn = (cp){cos(op * 2 * pi / le), sin(op * 2 * pi / le)};
for(int i = 0; i < N; i += le){
w = (cp){1, 0};
for(int j = 0; j < mid; j++){
x = a[i + j];
y = a[i + j + mid] * w;
a[i + j] = x + y;
a[i + j + mid] = x - y;
w = w * wn;
}
}
}
}
void D_FFT(cp *a, cp *b, int N, int op){
for(int i = 0; i < N; i++) d[i] = a[i] + I * b[i];
FFT(d, N, op);
d[N] = d[0];
if(op == 1){
for(int i = 0; i < N; i++){
a[i] = (d[i] + d[N - i].conj()) / 2;
b[i] = I * (-1) * (d[i] - d[N - i].conj()) / 2;
}
} else {
for(int i = 0; i < N; i++){
a[i] = cp(d[i].x, 0);
b[i] = cp(d[i].y, 0);
}
}
d[N] = cp(0, 0);
}
void mult(int *a, int *b, int M){
int N = 1, len = 0;
while(N < M) N <<= 1, len++;
for(int i = 0; i < N; i++)
wz[i] = (wz[i >> 1] >> 1) | ((i & 1) << (len - 1));
for(int i = 0; i < N; i++){
ka[i].x = a[i] >> 15;
kb[i].x = b[i] >> 15;
ta[i].x = a[i] & 32767;
tb[i].x = b[i] & 32767;
}
D_FFT(ta, ka, N, 1); D_FFT(tb, kb, N, 1);
for(int i = 0; i < N; i++){
kk[i] = ka[i] * kb[i];
kt[i] = ka[i] * tb[i] + ta[i] * kb[i];
tt[i] = ta[i] * tb[i];
}
D_FFT(tt, kk, N, -1); FFT(kt, N, -1);
for(int i = 0; i < N; i++){
tt[i] = tt[i] / N;
kt[i] = kt[i] / N;
kk[i] = kk[i] / N;
}
}
}PLE;
void main() {
scanf("%d%d%d", &n, &m, &p); n++; m++;
for(int i = 0; i < n; i++) scanf("%d", &a[i]),a[i] = a[i] % p;
for(int i = 0; i < m; i++) scanf("%d", &b[i]),b[i] = b[i] % p;
PLE.mult(a, b, n + m - 1);
for(int i = 0; i < n + m - 1; i++)
printf("%lld ",(((((LL)round(kk[i].x)) % p) << 30) + ((((LL)round(kt[i].x)) % p) << 15) + ((LL)round(tt[i].x)) % p) % p);
}
}
多项式求逆:
多项式ln:
把
对于终止情况:
因为都只剩下了常数项,
喜闻乐见的代码:
多项式
namespace PLE_ln{
struct polyme {
int li[SZ], wz[SZ];
void NTT(int *a, int N, int op) {
rop(i, 0, N) if(i < wz[i]) swap(a[i], a[wz[i]]);
for(int l = 2; l <= N; l <<= 1) {
int mid = l >> 1;
int x, y, w, wn = qpow(g, (mod - 1) / l);
if(op) wn = qpow(wn, mod - 2);
for(int i = 0; i < N; i += l) {
w = 1;
for(int j = 0; j < mid; ++j) {
x = a[i + j]; y = 1ll * w * a[i + j + mid] % mod;
a[i + j] = (x + y) % mod;
a[i + j + mid] = (x - y + mod) % mod;
w = 1ll * w * wn % mod;
}
}
}
}
void qd(int *a, int *b, int n) {
rop(i, 0, n) b[i] = 1ll * a[i + 1] * (i + 1) % mod;
}
void jf(int *a, int *b, int n) {
rop(i, 1, n) b[i] = 1ll * a[i - 1] * qpow(i, mod - 2) % mod;
}
void mult(int *a, int *b, int *c, int M) {
int N = 1, len = 0;
while(N < M) N <<= 1, len ++;
rop(i, 0, N) wz[i] = (wz[i >> 1] >> 1) | ((i & 1) << (len - 1));
NTT(a, N, 0); NTT(b, N, 0);
rop(i, 0, N) c[i] = 1ll * a[i] * b[i] % mod;
NTT(c, N, 1);
int t = qpow(N, mod - 2);
rop(i, 0, N) c[i] = 1ll * c[i] * t % mod;
}
void inv(int *a, int *b, int deg) {
if(deg == 1) {b[0] = qpow(a[0], mod - 2) % mod; return;}
inv(a, b, (deg + 1) >> 1);
rop(i, 0, deg) li[i] = a[i];
int N = 1, len = 0;
while(N < deg + deg - 1) N <<= 1, len ++;
rop(i, 0, N) wz[i] = (wz[i >> 1] >> 1) | ((i & 1) << (len - 1));
rop(i, deg, N) li[i] = 0;
NTT(li, N, 0); NTT(b, N, 0);
rop(i, 0, N) b[i] = 1ll * b[i] * (2 - 1ll * li[i] * b[i] % mod + mod) % mod;
NTT(b, N, 1);
int t = qpow(N, mod - 2);
for(int i = 0; i < N; i++) b[i] = 1ll * b[i] * t % mod;
rop(i, deg, N) b[i] = 0;
}
}PLE;
int a[SZ], da[SZ], ia[SZ], dla[SZ], la[SZ], n;
void main() {
scanf("%d", &n);
rop(i, 0, n) scanf("%d", &a[i]);
PLE.qd(a, da, n);
PLE.inv(a, ia, n);
PLE.mult(ia, da, dla, n + n - 1);
PLE.jf(dla, la, n);
rop(i, 0, n) printf("%d ", la[i]);
}
}
\color{red}{\text{多项式全集之五 多项式快速幂与开根:}}
多项式快速幂方法一:
直接套用快速幂模板,重载*运算符,
多项式快速幂方法二:
问题转化成求函数
多项式求逆即可。
结束状态:
\color{red}{\text{多项式全集之六 多项式多点求值与快速插值:}}
多项式多点求值 :
给定多项式
设:
显然当
我们设
那么
当
多项式快速插值 :
\color{red}{\text{多项式全集之八 本文提到知识的部分扩展:}}
麦克劳林级数:
泰勒公式中,取