发现自己的多项式水平菜到不行,所以写篇文章记录一下。
基础操作
FFT & NTT
基础知识。
考虑如何快速计算多项式乘法,发现常规的做法是没有前途的,考虑换种方法计算。
有一种计算方法是这样的(设要计算 A×B ):
- 将 A 转化为点值表示。
- 将 B 转化为点值表示。
- O(n) 地计算点值表示的乘积 C。
- 将 C 转化为数值表示。
复杂度瓶颈为点值表示与数值表示的转化,朴素做是 O(n2)。
为了加速这个过程,FFT 引入了单位复根 ω,NTT 引入了原根 g。
单位复根 ω 是 xn=1 的解集,其满足如下性质:
- ωnn=1;
- ωnk=ω2n2k;
- ω2nk+n=−ω2nk。
考虑分治,对一个多项式 f(x) 奇偶性分组处理,比如一个多项式:
f(x)=a0+a1x+a2x2+a3x3+a4x4+a5x5+a6x6+a7x7
考虑奇偶性分组后的状态:
f(x)=a0+a2x2+a4x4+a6x6+x(a1+a3x2+a5x4+a7x6)
建立新函数:
G(x)=a0+a2x+a4x2+a6x3H(x)=a1+a3+a5x2+a7x3
那么待求解的 f(x) 可改为:
f(x)=G(x2)+xH(x2)
带入 ω 可得:
DFT(f(ωnk))=DFT(G((ωnk)2))+ωnk×DFT(H((ωnk)2))=DFT(G(ωn2k))+ωnk×DFT(H(ωn2k))=DFT(G(ωn/2k))+ωnk×DFT(H(ωn/2k))DFT(f(ωnk+n/2))=DFT(G(ωn2k+n))+ωnk+n/2×DFT(H(ωn2k+n))=DFT(G(ωn2k))−ωnk×DFT(H(ωn2k))=DFT(G(ωn/2k))−ωnk×DFT(H(ωn/2k))
然后就可以分治做了。
诶等等,递归常数特别大呢!
为了不递归,我们考虑一开始就预处理出来每一项最后到了什么地方,这个做法是位逆序置换(又名蝴蝶置换)。
考虑找规律,发现将下标二进制表示后翻转即可,设 Ri 为 i 变到的地方,那么可以考虑这样求:
那么 Ri 的递推式可以轻易的写成:
Ri=⌊2R⌊2i⌋⌋+(imod2)×2len
以上就是如何将数值表示转化为点值表示了,所以怎样转回来呢?
直接反转后除以 len,或是取单位根的倒数跑即可,具体推导过程见 OI-Wiki。
模板:
void FFT(int n,cp* x,int op) {
for(int i=0;i<n;i++)
if(i<rk[i]) swap(x[i],x[rk[i]]);
for(int mid=1;mid<n;mid*=2) {
cp W(cos(Pi/mid),op*sin(Pi/mid));
for(int R=mid*2,j=0;j<n;j+=R) {
cp w(1,0);
for(int k=0;k<mid;k++,w=w*W) {
cp z1=x[j+k],z2=w*x[j+mid+k];
x[j+k]=z1+z2,x[j+mid+k]=z1-z2;
}
}
}
}
NTT 呢?
首先解释 g,g 是原根,在 modp 的情况下 ∀i=j,gi≡gj(modp)。
我们用 gnφ(p) 代替 ω 进行计算,由 φ(p)=p−1 且 n=2q,所以强制要求 p=k×2q+1,其中 2q≥n。
较 FFT 精度高,但却对模数有着致命的要求。
时间复杂度 O(nlogn)。
分治 FFT & NTT
问题:求 fn,其中:
fi=j=0∑i=1fjgi−j
考虑 CDQ 分治,对于一个区间 [l,r],将其划分成 [l,mid],[mid+1,r] 两段。
先处理 [l,mid],再计算 [l,mid] 对 [mid+1,r] 的贡献,再处理 [mid+1,r]。
中间的计算使用 NTT/FFT 解决,时间复杂度 O(nlog2n)
拉格朗日插值
问题,给定 n 个点 (xi,yi),确定一个多项式 y=f(x),求出 f(k) 的值。
显然的事实:
f(x)≡f(y)(modx−y)
证明:f(x)−f(y)=(a0−a0)+a1(x−y)+a2(x2−y2)+a3(x3−y3)+⋯。
那么我们就可以列一个线性同余方程组:
⎩⎪⎪⎪⎨⎪⎪⎪⎧f(x)≡y1(modx−y1)f(x)≡y2(modx−y2)…f(x)≡yn(modx−yn)
直接 CRT,所有模数的积为:
i=1∏n(x−yi)
那么 mi=∏j=i(x−yi),mi−1=∏j=ixi−xj1。
所以:
f(x)=i=1∑nyij=i∏xi−xjx−xj
朴素实现复杂度是 O(n2) 的。
在所取点值连续的时候,即 xi=i 的时候,可以直接用前缀积优化到 O(n)。
在需要动态加点的时候,可以使用重心拉格朗日插值法:
回顾之前的式子:
f(x)=i=1∑nyij=i∏xi−xjx−xj
设 g=∏i=1nk−xi,那么:
f(x)=gi=0∑n∏(k−xi)(xi−xj)yi
设 ti=∏j=ixi−xjyi,那么:
f(x)=gi=0∑nk−xiti
加入一个点只需计算 ti 即可。
求自然数幂和可以直接拉格朗日插值,时间复杂度 O(n)。
多项式求导与积分
由于求导和积分满足可加性,所以拆成单项式分别求导或积分。
如果你学过微积分相关理论,会知道:
A′(x)=i=1∑niaixi−1∫A(x)dx=(i=0∑ni+1aixi+1)+c
直接做即可,为数不多的 O(n) 时间复杂度。
多项式求逆
给定 G(x),求一个 F(x) 使得 F(x)G(x)≡0(modxn)。
首先假设我们已经求出了一个 f(x) 使得 f(x)G(x)≡0(modxn/2),考虑如何求出 F(x)。
因为 F(x)G(x)≡0(modxn),所以必然 F(x)G(x)≡0(modxn/2),两式相减,得 F(x)−f(x)≡0(modxn/2),此处 G(x) 被约掉了。
两边同时平方,得:
F2(x)−2F(x)f(x)+f2(x)≡0(modxn)
注意标红的部分,想一想,为什么?
考虑卷积的定义,在 [0,n/2] 的部分每一项都是 0,而右边 [n/2,n] 的部分由 ai=∑j=1iajai−j 得来,容易发现,j 与 i−j 中必有一个小于零。
两边同时乘以 G(x) 并移项,得:
F(x)≡2f(x)−G(x)f2(x)(modxn)
直接使用 FFT 计算即可,时间复杂度 O(nlogn)。
多项式开根
给定 g(x),求 f(x),使得:f2(x)≡g(x)(modxn)。
类似求逆,假设已经求出了 f02(x)≡g(x)(modxn/2)。
移项得:
f02(x)−g(x)≡0(modxn/2)
两边平方得:
(f02(x)−g(x))2≡0(modxn)
使用简单的数学公式 (x−y)2=(x+y)2−4xy 得:
(f02(x)+g(x))2≡4f02(x)g(x)(modxn)
将 4f02(x) 移到左边,得:
(2f0(x)f02(x)+g(x))2≡g(x)(modxn)
左右两边同时开根,得:
2f0(x)f02(x)+g(x)≡f(x)(modxn)
2−1f0(x)+2−1f0−1(x)g(x)≡f(x)(modxn)
直接计算即可,时间复杂度 O(nlogn)。
多项式带余除法
问题:给定 n 次多项式 F(x) 和 m 次多项式 G(x),求 Q(x) 与 R(x),满足:
- degQ(x)=n−m,degR(x)<m;
- F(x)=Q(x)×G(x)+R(x)。
对于一个多项式 f(x),定义 fR(x)=xnf(x1),等价于将多项式 f 的系数翻转一次。
那么:
F(x)=Q(x)×G(x)+R(x)F(x1)=Q(x1)×G(x1)+R(x1)xnF(x1)=xn−mQ(x1)×xmG(x1)+xn−m+1xm−1R(x1)FR(x)=QR(x)×GR(x)+xn−m+1RR(x)FR(x)≡QR(x)×GR(x)(modxn−m+1)QR(x)≡FR(x)×GR−1(x)(modxn−m+1)
直接求出 Q(x),那么回代可得 R(x),时间复杂度 O(nlogn)。
多项式对数函数
求 B(x)≡lnA(x)(modxn)。
设 F(x)=lnx,那么:
B(x)=F(A(x))B′(x)=F′(A(x))A′(x)B′(x)=A(x)A′(x)B(x)=∫A(x)A′(x)dx
直接模拟即可,时间复杂度 O(nlogn)。
多项式指数函数
求 F(x)≡eA(x)(modxn)。
考虑多项式牛顿迭代。
多项式牛顿迭代解决的问题是,给定函数 G(x),求 G(F(x))≡0(modxn)。
既然是迭代,那么当 n=1 的时候是要单独求出的,这里不讲。
类似求逆,假设已经求出 G(F0(x))≡0(modx⌈xn⌉),考虑如何扩展。
把 G(F(x)) 在 F0(x) 处泰勒展开:
G(F(x))=G(F0(x))+1!G′(F0(x))(F(x)−F0(x))+2!G′′(F0(x))(F(x)−F0(x))+…
因为 F(x) 和 F0(x) 的最后几项相同,所以 (F(x)−F0(x))2 的最低非 0 次数大于 n,所以可以得到:
G(F(x))≡G(F0(x))+G′(F0(x))(F(x)−F0(x))(modxn)G(F0(x))+G′(F0(x))(F(x)−F0(x))≡0(modxn)G(F0(x))+G′(F0(x))F(x)−G′(F0(x))F0(x)≡0(modxn)G′(F0(x))F(x)≡G′(F0(x))F0(x)−G(F0(x))(modxn)F(x)≡F0(x)−G′(F0(x))G(F0(x))(modxn)
那么,将 F(x)=eA(x) 变形得 lnF(x)−A(x)=0。
设 G(F(x))=lnF(x)−A(x),那么 G′(F(x))=F(x)1,带入牛顿迭代公式得:
F(x)≡F0(x)−F0(x)lnF0(x)−A(x)(modxn)F(x)≡F0(x)(1−lnF0(x)+A(x))(modxn)
直接做,时间复杂度 O(nlogn)。
参考代码
不想太长,所以放一份在剪贴板。