文章目录
基于NTT的多项式操作
因为实在是太多东西啦,所以就全部整理了以下,持续更新ing~
前置知识:NTT
顺便说一句,代码采用重载vector封装的形式(因为懒得自己开结构体)
下面是几个已经封装的基础代码,为了方便浏览,先贴出来:
typedef std::vector<int> VI;
int fix(int x) {
return (x >> 31 & P) + x;}
int Pow(int x, int k) {
int r = 1;
for(;k; k >>= 1, x = 1LL * x * x % P)
if(k & 1)
r = 1LL * r * x % P;
return r;
}
int Inv(int x) {
return Pow(x, P - 2);}
void Pre(int m) {
int x = 0; L = 1;
for(;(L <<= 1) < m; ++x) ;
for(int i = 1;i < L; ++i)
R[i] = R[i >> 1] >> 1 | (i & 1) << x;
int wn = Pow(3, (P - 1) / L); w[0] = 1;
for(int i = 1;i < L; ++i)
w[i] = 1LL * w[i - 1] * wn % P;
InvL = Inv(L);
}
void NTT(int *F) {
for(int i = 0;i < L; ++i)
if(R[i] > i)
std::swap(F[i], F[R[i]]);
for(int i = 1, d = L >> 1; i < L; i <<= 1, d >>= 1)
for(int j = 0;j < L; j += i << 1) {
int *l = F + j, *r = F + i + j, *p = w, tp;
for(int k = i; k--; ++l, ++r, p += d)
tp = 1LL * *p * *r % P, *r = (*l - tp) % P, *l = (*l + tp) % P;
}
}
void Fill(const VI &a, int *A, int m) {
m = std::min(m, (int)a.size());
for(int i = 0;i < m; ++i)
A[i] = a[i];
for(int i = m; i < L; ++i)
A[i] = 0;
}
void Fill(int *A, int *B, int m) {
for(int i = 0;i < m; ++i)
B[i] = A[i];
for(int i = m; i < L; ++i)
B[i] = 0;
}
VI operator * (const VI &a, const VI &b) {
const int Lim = 3000;
int m = a.size() + b.size() - 1;
static VI c; static int A[N], B[N];
c.resize(m, 0);
if(a.size() * b.size() <= Lim) {
//小范围暴力
for(int i = 0;i < m; ++i)
c[i] = 0;
for(int i = 0;i < a.size(); ++i)
for(int j = 0;j < b.size(); ++j)
c[i + j] = (c[i + j] + 1LL * a[i] * b[j]) % P;
return c;
}
Pre(m);
Fill(a, A, a.size());
Fill(b, B, b.size());
NTT(A); NTT(B);
for(int i = 0;i < L; ++i)
A[i] = 1LL * A[i] * B[i] % P;
NTT(A);
for(int i = 0;i < m; ++i)
c[i] = fix(1LL * A[L - i & L - 1] * InvL % P);
return c;
}
VI operator - (const VI &a, const VI &b) {
int n = std::max(a.size(), b.size());
static VI c; c.resize(n, 0);
for(int i = 0;i < a.size(); ++i)
c[i] = a[i];
for(int i = 0;i < b.size(); ++i)
c[i] = fix(c[i] - b[i]);
return c;
}
VI operator + (const VI &a, const VI &b) {
int n = std::max(a.size(), b.size());
static VI c; c.resize(n, 0);
for(int i = 0;i < a.size(); ++i)
c[i] = a[i];
for(int i = 0;i < b.size(); ++i)
c[i] = (c[i] + b[i]) % P;
return c;
}
迭代相关:多项式求逆,开根,求exp
这些我们熟悉的多项式操作都是采用迭代法解决,既然都是迭代法,必有其共性,因此下面便脱离传统的证明方法,采用牛顿迭代法来进行一波推导
知识点:泰勒展开
下面首先介绍一下泰勒展开:
对于一个函数 f ( x ) f(x) f(x),其在 x = x 0 x=x_0 x=x0处的泰勒公式如下:
f ( x ) = f ( x 0 ) 0 ! + f ′ ( x 0 ) 1 ! ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ! ( x − x 0 ) 2 ⋯ f ( n ) ( x 0 ) n ! ( x − x 0 ) n f(x)=\frac{f(x_0)}{0!}+\frac{f'(x_0)}{1!}(x-x_0)+\frac{f''(x_0)}{2!}(x-x_0)^2\cdots\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n f(x)=0!f(x0)+1!f′(x0)(x−x0)+2!f′′(x0)(x−x0)2⋯n!f(n)(x0)(x−x0)n
知识点:牛顿迭代
这是一种逼近零点的科技。(高中数学课本上有,文化课选手应该了解)
对于一个函数 f ( x ) f(x) f(x),我们考虑以 x i x_i xi作为起点逼近其零点。
方法就是,作 ( x i , y i ) (x_i,y_i) (xi,yi)处的切线,设其交 x x x轴于 x i + 1 x_{i+1} xi+1,以 x i + 1 x_{i+1} xi+1作为下一次的起点重复上述过程不断逼近零点。
推一推式子:
y − f ( x i ) = f ′ ( x i ) ( x − x i ) y-f(x_i)=f'(x_i)(x-x_i) y−f(xi)=f′(xi)(x−xi)
令 y = 0 y=0 y=0,得到 x i + 1 = x i − f ( x i ) f ′ ( x i ) x_{i+1}=x_i-\frac{f(x_i)}{f'(x_i)} xi+1=xi−f′(xi)f(xi)
有没有发现式子其实挺熟悉的?
考虑直线方程: y = f ( x i ) + f ′ ( x i ) ( x − x i ) y=f(x_i)+f'(x_i)(x-x_i) y=f(xi)+f′(xi)(x−xi)
发现直线方程就是 f ( x ) f(x) f(x)在 x i x_i xi处的一阶泰勒展开。
形式化地,我们可以这样描述牛顿迭代法:
用函数 f ( x ) f(x) f(x)在 f ( x i ) f(x_i) f(xi)处的一阶展开方程的零点作为下一次迭代的起点 x i x_i xi,这样的迭代法就是牛顿迭代法。
这个东西对于多项式有什么好处呢?
我们不难发现定义一个以多项式为自变量的函数 G ( x ) G(x) G(x),泰勒公式仍然成立。
我们首先将题目的形式转化为求 F ( x ) ∣ G ( F ( x ) ) ≡ 0 ( m o d    x n ) F(x)|G(F(x))\equiv 0(\mod x^n) F(x)∣G(F(x))≡0(modxn)
迭代法的一般考虑步骤是:
已知 G ( F 0 ( x ) ) ≡ 0 ( m o d    x n ) G(F_0(x))\equiv 0(\mod x^n) G(F0(x))≡0(modxn)
求 G ( F ( x ) ) ≡ 0 ( m o d    x 2 n ) G(F(x))\equiv 0 (\mod x^{2n}) G(F(x))≡0(modx2n)
考虑 G ( x ) G(x) G(x)在 F 0 ( x ) F_0(x) F0(x)处的泰勒展开逼近 F ( x ) F(x) F(x)
G ( F ( x ) ) ≡ G ( F 0 ( x ) ) + G ′ ( F 0 ( x ) ) ( F ( x ) − F 0 ( x ) ) + G ′ ′ ( F 0 ( x ) ) ( F ( x ) − F 0 ( x ) ) 2 ⋯ ( m o d    x n ) G(F(x))\equiv G(F_0(x))+G'(F_0(x))(F(x)-F_0(x))+G''(F_0(x))(F(x)-F_0(x))^2\cdots(\mod x^n) G(F(x))≡G(F0(x))+G′(F0(x))(F(x)−F0(x))+G′′(F0(x))(F(x)−F0(x))2⋯(modxn)
事实上,我们有 F ( x ) ≡ F 0 ( x ) ( m o d    x n ) F(x)\equiv F_0(x) (\mod x^n) F(x)≡F0(x)(modxn)
那么 F ( x ) − F 0 ( x ) ≡ 0 ( m o d    x n ) ⇒ ( F ( x ) − F 0 ( x ) ) 2 ≡ 0 ( m o d    x 2 n ) F(x)-F_0(x)\equiv 0 (\mod x^n)\Rightarrow (F(x)-F_0(x))^2\equiv 0 (\mod x^{2n}) F(x)−F0(x)≡0(modxn)⇒(F(x)−F0(x))2≡0(modx2n)
这一步推导是因为 F ( x ) − F 0 ( x ) F(x)-F_0(x) F(x)