前言
搞完多项式乘法之后,其实我就想把多项式的这一堆都做了,但是鸽了很久都没动。。
最近正在一点点啃,笔记都会记在这里。To do list:
多项式快速插值 \text{Interpolation}Interpolation
\text{Part1 万恶之源——多项式乘法}Part1 万恶之源——多项式乘法
在我的这篇blog里,较为详细地讲了FFT,借由这个算法,可以实现\Theta(n\log n)Θ(nlogn)的多项式乘法(卷积)。
可是FFT要用到复数,这使得精度没办法保障,而且还不能取模。
那有没有别的方法解决这个问题呢?
答案很自然地是肯定的。
在数论中,有一个叫做原根的东西。简单来说,质数pp的原根定义是这样的:
若整数g^i\mod pgimodp的结果两两不同,且g\in[2,p-1]g∈[2,p−1],i\in[1,p-1]i∈[1,p−1],那么gg就是pp的原根。
比如质数998244353998244353的原根就是33
这个原根的用处就在于,它有着和FFT中的单位根\omegaω相似的性质!
利用这个性质我们就可以实现快速数论变换NTT(\text{Number Theory Transform}Number Theory Transform)
在那篇FFT的代码中,有一行:
complex rt = complex(cos(pi/mid),type*sin(pi/mid));
其中\text{type}=1type=1时,单位根为e^\frac{i\pi}{mid}emidiπ。
而在NTT中,“单位根”就成了:g^{\frac{p-1}{2mid}}g2midp−1,这里的gg就是原根。
一般我们取p=998244353,g=3p=998244353,g=3,方便计算。
然后我们就能打出NTT的板子了,和FFT极为相近:
void NTT(int *a,int type,int lim){
for(int i=0;i<=lim;++i){
if(i>=rev[i]) continue;
swap(a[i],a[rev[i]]); //这里的rev同FFT中的翻转数组
}
for(int mid=1;mid<lim;mid<<=1){
int rt = power(3,(p-1)/(mid<<1)); //上述的单位根计算方法
if(type==-1) rt = power(rt,p-2); //如果是逆变换,单位根要变逆元
int r = mid<<1;
for(int j=0;j<lim;j+=r){
int w = 1;
for(int k=0;k<mid;++k){
int x = a[j+k];
int y = (ll)w*a[j+k+mid]%p;
a[j+k] = (x+y)%p;
a[j+k+mid] = ((x-y)%p+p)%p;
w = (ll)w*rt%p;
}
}
}
}
用NTT计算多项式乘法,和FFT一样,最后出来的系数要除以\limlim (也就是乘\limlim在模pp下的逆元)
ps:用NTT比FFT常数小了不少呢!
\text{Part2 强力工具——多项式求逆}Part2 强力工具——多项式求逆
在做有关多项式的题时,我们经常遇到这样的问题:
给定一个n-1n−1次多项式F(x)F(x)求一个多项式G(x)G(x),使其满足:
G(x)F(x)\equiv1\space (\text{mod }x^n)G(x)F(x)≡1 (mod xn)
这里模x^nxn的意义就在于,忽略次数\ge n≥n的项。
这个东西好像没法直接求,我们设一个多项式G_1(x)G1(x),满足条件:
G_1(x)F(x)\equiv1\space (\text{mod }x^{\lceil\frac{n}{2}\rceil})G1(x)F(x)≡1 (mod x⌈2n⌉)
和刚才那个式子做差,可以得到:
F(x)(G(x)-G_1(x))\equiv0\space (\text{mod }x^{\lceil\frac{n}{2}\rceil})F(x)(G(x)−G1(x))≡0 (mod x⌈2n⌉)
稍微化简一下,也就是
G(x)-G_1(x)\equiv0\space (\text{mod }x^{\lceil\frac{n}{2}\rceil})G(x)−G1(x)≡0 (mod x⌈