前言:
这可能是本蒟蒻在2018省选以前写的最难的东西了。
考虑到时间紧迫,所以有些东西我自己也不能完全理解,只能照搬课件。
只能以后再看机会填坑了
再说回多项式,几乎所有的多项式算法都是基于NTT的优化,也就是说,要想办法把所有的运算,转化为可以使用NTT优化的表达方式。
首先给出几个在之后的证明中可能用到的概念:
一个多项式
A
A
A的最高次幂,称为该多项式的度,记为
d
e
g
A
degA
degA
多项式求逆
用数学语言表示,即对于一个多项式
A
(
x
)
A(x)
A(x)
要找出一个对应的
B
(
x
)
B(x)
B(x)满足
(
d
e
g
B
<
d
e
g
A
)
(degB < degA)
(degB<degA)且
A
(
x
)
B
(
x
)
≡
1
(
m
o
d
(
x
n
)
)
A(x)B(x)\equiv 1(mod(x^n))
A(x)B(x)≡1(mod(xn))
用倍增的思路考虑这个问题,假设我们已经得到了一个
B
′
(
x
)
B'(x)
B′(x)使得
A
(
x
)
B
′
(
x
)
≡
1
(
m
o
d
(
x
⌊
n
2
⌋
)
)
A(x)B'(x)\equiv 1(mod(x^{\lfloor \frac n 2\rfloor }))
A(x)B′(x)≡1(mod(x⌊2n⌋))
现在希望通过
B
′
(
x
)
B'(x)
B′(x)与
A
(
x
)
A(x)
A(x)来表示出
B
(
x
)
B(x)
B(x)
首先,很显然
A
(
x
)
B
(
x
)
≡
1
(
m
o
d
(
x
⌊
n
2
⌋
)
)
A(x)B(x) \equiv 1(mod(x^{\lfloor \frac n 2\rfloor }))
A(x)B(x)≡1(mod(x⌊2n⌋))
所以很显然
B
(
x
)
−
B
′
(
x
)
≡
0
(
m
o
d
(
x
⌊
n
2
⌋
)
)
B(x)-B'(x)\equiv 0(mod(x^{\lfloor \frac n 2\rfloor }))
B(x)−B′(x)≡0(mod(x⌊2n⌋))
两边同时平方,得到
B
2
(
x
)
+
B
′
2
(
x
)
−
2
B
(
x
)
B
′
(
x
)
≡
0
(
m
o
d
(
x
n
)
)
B^2(x)+B'^2(x)-2B(x)B'(x)\equiv 0 (mod(x^n))
B2(x)+B′2(x)−2B(x)B′(x)≡0(mod(xn))
最神奇的一点就是,为什么平方过后,模数也可以跟着平方了:
由于原来左边的多项式在0到
n
−
1
n-1
n−1项的系数均为0,平方过后,每个在0到
2
∗
n
−
1
2*n-1
2∗n−1之间的项,必有一个因子来源于
0
0
0到
n
−
1
n-1
n−1,则说明平方过后,在0到
2
∗
n
−
1
2*n-1
2∗n−1之间的项的系数仍然为0,所以在模
x
n
x^n
xn下也为0。
代码:
void inv(int n,int *A,int *B){
if(n==1){
B[0]=fsp(A[0],MOD-2);
return ;
}
inv((n+1)>>1,A,B);
static int X[MAXN];
int p=1;
for(;p<(n<<1);p<<=1);
for(int i=0;i<n;i++)
X[i]=A[i];
for(int i=n;i<p;i++)
X[i]=0;
ntt(X,p,1);
for(int i=(n+1)>>1;i<p;i++)
B[i]=0;
ntt(B,p,1);
for(int i=0;i!=p;i++)
B[i]=(ll)B[i]*(2+MOD-(ll)X[i]*B[i]%MOD)%MOD;
ntt(B,p,-1);
}
多项式除法及取模
给出多项式
A
(
x
)
、
B
(
x
)
A(x)、B(x)
A(x)、B(x),求出多项式
D
(
x
)
、
R
(
x
)
D(x)、R(x)
D(x)、R(x)
满足
A
(
x
)
=
D
(
x
)
B
(
x
)
+
R
(
x
)
A(x)=D(x)B(x)+R(x)
A(x)=D(x)B(x)+R(x)
令
n
=
d
e
g
A
,
m
=
d
e
g
B
n=degA,m=degB
n=degA,m=degB
并且
d
e
g
D
≤
d
e
g
A
−
d
e
g
B
=
n
−
m
、
d
e
g
R
≤
m
degD\leq degA-degB=n-m、degR\leq m
degD≤degA−degB=n−m、degR≤m
定义一个
A
R
(
x
)
=
x
n
A
(
1
x
)
A^R(x)=x^nA(\frac 1 x)
AR(x)=xnA(x1)
即
A
R
(
x
)
为
A
(
x
)
A^R(x)为A(x)
AR(x)为A(x)将系数按次数反转之后得到的多项式
现在将原式中x替换为
1
x
\frac 1 x
x1,得到一个新的式子,并在左右同乘以
x
n
x^n
xn:
x
n
A
(
1
x
)
=
x
n
−
m
D
(
1
x
)
x
m
B
(
1
x
)
+
x
n
−
m
+
1
x
m
−
1
R
(
1
x
)
x^nA(\frac 1 x)=x^{n-m}D(\frac 1 x)x^mB(\frac 1 x)+x^{n-m+1}x^{m-1}R(\frac 1 x)
xnA(x1)=xn−mD(x1)xmB(x1)+xn−m+1xm−1R(x1)
进而得到
A
R
(
x
)
=
D
R
(
x
)
B
R
(
x
)
+
x
n
−
m
+
1
R
R
(
x
)
A^R(x)=D^R(x)B^R(x)+x^{n-m+1}R^R(x)
AR(x)=DR(x)BR(x)+xn−m+1RR(x)
其实就是将原式转换为用
X
R
(
x
)
X^R(x)
XR(x)来表示
这样一来,可以使用老套路:更换模数(其实上面的求逆本质上也是这个)
发现: D R ( x ) D^R(x) DR(x)的最高次项不高于 x n − m x^{n-m} xn−m,但是很显然 x n − m + 1 R R ( x ) x^{n-m+1}R^R(x) xn−m+1RR(x)的最低次项都高于了 x n − m x^{n-m} xn−m,那么在模 x n − m + 1 x^{n-m+1} xn−m+1次项的意义下,就可以消除 R R ( x ) R^R(x) RR(x)带来的影响。
所以,
A
R
(
x
)
≡
B
R
(
x
)
D
R
(
x
)
(
m
o
d
(
x
n
−
m
+
1
)
)
A^R(x)\equiv B^R(x)D^R(x) (mod(x^{n-m+1}))
AR(x)≡BR(x)DR(x)(mod(xn−m+1))
这样一来,就很好求出
D
R
(
x
)
D^R(x)
DR(x)了(通过上面讲的求逆实现),将
D
(
x
)
D(x)
D(x)求出后,再代入
A
(
x
)
=
B
(
x
)
D
(
x
)
+
R
(
x
)
A(x)=B(x)D(x)+R(x)
A(x)=B(x)D(x)+R(x),就可以求出
R
(
x
)
R(x)
R(x)
代码(这里是取模的代码,求商只需要前半部分即可):
void PolyMod(ll A[],ll B[],int N,int M){
static ll ta[MAXN],tb[MAXN],tmp[MAXN];
for(int i=0;i<N;i++) ta[i]=A[N-i-1];
for(int i=0;i<M;i++) tb[i]=B[M-i-1];
inv(tb,tmp,M);
for(int i=0;i<N-M+1;i++) tb[i]=tmp[i];
for(int i=0;i<4*M;i++) tmp[i]=0;
mul(ta,tb,N,N-M+1,tmp);
reverse(tmp,tmp+N-M+1);
mul(B,tmp,M,N-M+1,tmp);
for(int i=0;i<M-1;i++)
A[i]=(A[i]-tmp[i]+MOD)%MOD;
for(int i=0;i<4*(N+1);i++)
tmp[i]=0;
}
多项式多点求值与快速插值
多点求值
多点求值,即对多个点同时求其在多项式中的值,即对于一个数 x x x求出 A ( x ) A(x) A(x)的值,并且有多个数同时求值。
这个问题需要二分的思想。
将当前的多项式系数分为两个部分
X
[
0
]
=
x
0
,
x
1
,
x
2
,
…
…
x
n
2
X^{[0]}={x_0,x_1,x_2,……x_{\frac n 2}}
X[0]=x0,x1,x2,……x2n
X
[
1
]
=
x
n
2
+
1
,
x
n
2
+
2
,
…
…
,
x
n
−
1
X^{[1]}={x_{\frac n 2+1},x_{\frac n 2+2},……,x_{n-1}}
X[1]=x2n+1,x2n+2,……,xn−1
设
A
[
0
]
A^{[0]}
A[0]表示各点在
X
[
0
]
X^{[0]}
X[0]上的取值,用
A
[
1
]
A^{[1]}
A[1]表示各点在
X
[
1
]
X^{[1]}
X[1]上的取值。
即,我们将原来的多项式按次数大小分成了两个部分,这时如果能够快速求出
A
[
0
]
A^{[0]}
A[0]与
A
[
1
]
A^{[1]}
A[1],并且能够在较短时间内合并这两个值,就能得到所有点在当前多项式上的值。
再设两个多项式为:
P
[
0
]
(
x
)
=
∏
i
=
0
n
2
(
x
−
x
i
)
P^{[0]}(x)=\prod_{i=0}^{\frac n 2}(x-x_i)
P[0](x)=i=0∏2n(x−xi)
P
[
1
]
(
x
)
=
∏
i
=
n
2
+
1
n
(
x
−
x
i
)
P^{[1]}(x)=\prod_{i=\frac n 2+1}^{n}(x-x_i)
P[1](x)=i=2n+1∏n(x−xi)
显然,当
x
∈
X
[
0
]
x\in X^{[0]}
x∈X[0]时,
P
[
0
]
(
x
)
=
0
P^{[0]}(x)=0
P[0](x)=0,当
x
∈
X
[
1
]
x\in X^{[1]}
x∈X[1]时,
P
[
1
]
(
x
)
=
0
P^{[1]}(x)=0
P[1](x)=0
将A(x)分别除以
P
[
0
]
(
x
)
P^{[0]}(x)
P[0](x)
A
(
x
)
=
D
(
x
)
P
[
0
]
(
x
)
+
A
[
0
]
(
x
)
A(x)=D(x)P^{[0]}(x)+A^{[0]}(x)
A(x)=D(x)P[0](x)+A[0](x)
至于为什么可以这么写,原因在于当
x
∈
X
[
0
]
x\in X^{[0]}
x∈X[0]时,
P
[
0
]
P^{[0]}
P[0]为0
此时
A
(
x
)
=
A
[
0
]
(
x
)
A(x)=A^{[0]}(x)
A(x)=A[0](x),
也就是说,
A
(
x
)
≡
A
[
0
]
(
m
o
d
(
P
[
0
]
(
x
)
)
)
A(x)\equiv A^{[0]} (mod(P^{[0]}(x)))
A(x)≡A[0](mod(P[0](x)))
这里再运用上面的多项式取模,就可以得到所有点对于这部分的值。再同理求出另一部分即可。
板题与代码链接
快速插值
插值是指:对于一些下标,对应一些取值,求一个多项式满足这些取值
对于这部分,首先应去了解一下拉格朗日插值法。复杂度为
O
(
n
2
)
O(n^2)
O(n2)
现在考虑优化
同样地,考虑
X
[
0
]
=
x
i
,
y
i
(
0
≤
i
≤
n
2
)
X^{[0]}={x_i,y_i}(0\leq i \leq \frac n 2)
X[0]=xi,yi(0≤i≤2n)
X
[
2
]
=
x
i
,
y
i
(
n
2
+
1
≤
i
≤
n
)
X^{[2]}={x_i,y_i}(\frac n 2+1\leq i \leq n)
X[2]=xi,yi(2n+1≤i≤n)
同样设
A
[
0
]
(
x
)
A^{[0]}(x)
A[0](x)表示满足
X
[
0
]
X^{[0]}
X[0]的多项式
A
[
1
]
(
x
)
A^{[1]}(x)
A[1](x)表示满足
X
[
1
]
X^{[1]}
X[1]的多项式
设 P ( x ) = ∏ i = 0 i ≤ n 2 ( x − x i ) P(x)=\prod_{i=0}^{i\leq \frac n 2}(x-x_i) P(x)=i=0∏i≤2n(x−xi)
现在再来构造
A
(
x
)
A(x)
A(x),使其满足
A
(
x
)
=
A
[
1
]
(
x
)
P
(
x
)
+
A
[
0
]
(
x
)
A(x)=A^{[1]}(x)P(x)+A^{[0]}(x)
A(x)=A[1](x)P(x)+A[0](x)
然而我们并不知道
A
[
1
]
(
x
)
A^{[1]}(x)
A[1](x)
但很容易发现一点,所有在
X
[
0
]
X^{[0]}
X[0]中的插值在
P
(
x
)
P(x)
P(x)中均为0,也就是说,要满足这部分值,就必须在
A
[
0
]
A^{[0]}
A[0]中满足。
现在只需要使得所有不在
X
[
0
]
X^{[0]}
X[0]中的值也能满足,
即对于这部分值,只需要满足
y
i
=
A
[
1
]
(
x
i
)
P
(
x
i
)
+
A
[
0
]
y_i=A^{[1]}(x_i)P(x_i)+A^{[0]}
yi=A[1](xi)P(xi)+A[0]
所以可以通过这部分值,先把
A
[
1
]
A^{[1]}
A[1]求出来再合并即可。
O
(
N
l
o
g
3
N
)
O(Nlog^3N)
O(Nlog3N)算法代码(即上述方法)
O
(
N
l
o
g
2
N
)
O(Nlog^2N)
O(Nlog2N)算法代码
牛顿迭代法
牛顿迭代法是给出 G ( x ) G(x) G(x),求满足 G ( F ( x ) ) ≡ 0 ( m o d ( x n ) ) G(F(x))\equiv 0(mod (x^n)) G(F(x))≡0(mod(xn))的 F ( x ) F(x) F(x)
还是分治的思路,合并时用到了泰勒展开公式。
假设我们已经求出
F
0
(
x
)
F_0(x)
F0(x)满足
G
(
F
0
(
x
)
)
≡
0
(
m
o
d
(
x
n
2
)
)
G(F_0(x))\equiv 0(mod(x^{\frac n 2}))
G(F0(x))≡0(mod(x2n))
现在要求
F
(
x
)
F(x)
F(x),对
G
(
F
(
x
)
)
G(F(x))
G(F(x))在
F
0
(
x
)
F_0(x)
F0(x)上泰勒展开,得到
G ( F ( x ) ) = G ( F 0 ( x ) ) + G ′ ( F 0 ( x ) ) 1 ! ( F ( x ) − F 0 ( x ) ) 1 + G ′ ′ ( F 0 ( x ) ) 2 ! ( F ( x ) − F 0 ( x ) ) 2 … … G(F(x))=G(F_0(x))+\frac {G'(F_0(x))} {1!}(F(x)-F_0(x))^1+\frac {G''(F_0(x))} {2!}(F(x)-F_0(x))^2…… G(F(x))=G(F0(x))+1!G′(F0(x))(F(x)−F0(x))1+2!G′′(F0(x))(F(x)−F0(x))2……
由于泰勒展开公式是无限长的,所以这里要考虑优化。
由于
d
e
g
(
F
(
x
)
−
F
0
(
x
)
)
2
≥
n
deg(F(x)-F_0(x))^2\geq n
deg(F(x)−F0(x))2≥n所以在第三项及以后,在模
x
n
x^n
xn意义下均为0,可以忽略。
所以
G
(
F
(
x
)
)
=
G
(
F
0
(
x
)
)
+
G
′
(
F
0
(
x
)
)
(
F
(
x
)
−
F
0
(
x
)
)
(
m
o
d
(
x
n
)
)
G(F(x))=G(F_0(x))+{G'(F_0(x))}(F(x)-F_0(x))(mod(x^n))
G(F(x))=G(F0(x))+G′(F0(x))(F(x)−F0(x))(mod(xn))
再根据定义消掉
G
(
F
(
x
)
)
G(F(x))
G(F(x))
得到
F
(
x
)
≡
F
0
(
x
)
−
G
(
F
0
(
x
)
)
G
′
(
F
0
(
x
)
)
F(x)\equiv F_0(x)-\frac {G(F_0(x))} {G'(F_0(x))}
F(x)≡F0(x)−G′(F0(x))G(F0(x))
牛顿迭代法的具体应用:多项式开根
为什么说牛顿迭代法是很强的东西呢,因为用这个方法,可以极快速地得到一些结论,比如多项式开根:
给定
P
(
x
)
P(x)
P(x),求
F
(
x
)
F(x)
F(x)满足
F
(
x
)
2
−
P
(
x
)
≡
0
(
m
o
d
(
x
n
)
)
F(x)^2-P(x)\equiv 0(mod(x^n))
F(x)2−P(x)≡0(mod(xn))
我们可以将
F
(
x
)
F(x)
F(x)视为这个函数的变量,然后根据牛顿迭代法得到的结论:
F
(
x
)
=
F
0
(
x
)
−
F
0
(
x
)
2
−
P
(
x
)
2
∗
F
0
(
x
)
F(x)=F_0(x)-\frac {F_0(x)^2-P(x)} {2*F_0(x)}
F(x)=F0(x)−2∗F0(x)F0(x)2−P(x)
这里稍微讲一讲:
如果令牛顿迭代法中的
G
(
F
(
x
)
)
=
F
(
x
)
2
−
P
(
x
)
G(F(x))=F(x)^2-P(x)
G(F(x))=F(x)2−P(x),那么就能够代入了,原公式为
F
(
x
)
=
F
0
(
x
)
−
G
(
F
0
(
x
)
)
G
′
(
F
0
(
x
)
)
F(x)=F_0(x)-\frac {G(F_0(x))} {G'(F_0(x))}
F(x)=F0(x)−G′(F0(x))G(F0(x))
G
′
(
x
)
G'(x)
G′(x)为
G
(
x
)
G(x)
G(x)的导数,当
G
(
x
)
=
F
0
(
x
)
2
−
P
(
x
)
G(x)=F_0(x)^2-P(x)
G(x)=F0(x)2−P(x)时,
G
′
(
x
)
=
2
∗
F
0
(
x
)
G'(x)=2*F_0(x)
G′(x)=2∗F0(x)(dza大佬 ORZ)
把这些全部代入原式,就能得到该公式。
void Sqrt(int *a,int *b,int len){
if(len==1){
b[0]=a[0];//?
return ;
}
Sqrt(a,b,len>>1);
static int c[MAXN],d[MAXN];
for(int i=0;i<=len;i++)
c[i]=a[i];
Inv(b,d,len);
ntt(c,len<<1,1);
ntt(d,len<<1,1);
for(int i=0;i<(len<<1);i++)
d[i]=1ll*d[i]*c[i]%MOD;
ntt(d,len<<1,-1);
for(int i=0;i<len;i++)
b[i]=1ll*(1ll*d[i]+b[i])*inv2%MOD;
for(int i=0;i<=(len<<1);i++)
c[i]=d[i]=0;
}
同样的方法还可以推多项式求逆
其他多项式算法(补)
结语
多项式算法的一些练习题
多项式求逆:BZOJ3456其实后面的题,基本都用到了多项式求逆
多项式取模:BZOJ4944(NOI2017 D1T3),这道题中多项式取模感觉有点鸡肋,因为不用多项式取模照样能暴力过。但如果当
k
≥
20000
k\geq 20000
k≥20000时,就只能用多项式取模了。
多项式(除法?)取模:CodeChef RNG,这道题是上一题的简化版,不过必须使用多项式取模。
多项式开根:CF438E The Child And Binary Tree有点分析过程,如果不想分析只想练板可以直接查题解。
另外,很多算法在洛谷中有模板题,建议都打一发。