多项式入门


先贴个tp(其实是自己不会

呜噜噜多项式普及

呜噜噜多项式提高


关于多项式

它一般以这样的形态出现
A(x)=a0+a1x+a2x2++anxnA(x)=a0+a1x+a2x2++anxn
其中 xx 被称作自变量(?),对应的每个 a0,a1,a2a0,a1,a2称为这个多项式的系数,对于这个多项式的最高次数 nn,我们称之为这个多项式的 度degree

对于每个题目而言,我们知道的同时需要操作的一般也就是它的系数和它的度

多项式加减乘除!

为什么没有除呢?因为我还不会(现在我会啦!。两个多项式的加减即对应项系数的加减,我们可以 O(n)O(n) 简单秒掉,但是多项式相乘呢?

这里我们引入一个新的概念(至少我是从这里才知道的)—— 卷积

形如ci=ij=0aj×bijci=ij=0aj×bij 中的 cici 我们称之为 a,ba,b 两个数组的卷积

那么两个多项式相乘,实际上就是求两个多项式的卷积即cixi=ij=0ajxj×bijxijcixi=ij=0ajxj×bijxij,同时原本度都为为 nn 的两个多项式相乘后得到的多项式的度也就变成了 2n2n .

快速傅里叶变换(FFT)

通过定义式,我们知道两个多项式相乘的复杂度是 O(n2)O(n2) 级别的,这对于大多数的题目而言都太慢了,所以我们需要更优秀的算法,多项式的快速变换也就应运而生

对于一个多项式,我们不仅有系数表达(就是以上所用的表达),还有点值表达

就好像 n+1n+1 个点就可以确定一个次数为 nn 的函数图像一样,一个度为 nn 多项式也可以转化为 n+1n+1 个点值

如何转换

首先我们又得引入几个新的概念:

复数

我们一般接触的数都属于实数范围,但还有一些不合法,但是依然存在的数,形如11(在下文中,我们称之为 ii ),我们称之为虚数.

而复数则是既含有实数,有含有虚数的数。一般来说,我们称复数的实数部分为实部,虚数部分的系数为虚部

复数之间的运算也同我们熟知的实数一样

单位复数根

我们设度为nn 的多项式的单位复数根为ωn=e2πi/n=cos(2π/n)+isin(2π/n)ωn=e2πi/n=cos(2π/n)+isin(2π/n)

它有着非常好的性质

ωnn=1ωnn=1

ω2d2n=ωdnω2d2n=ωdn

ωd+n2n=ωd2nωd+n2n=ωd2n

nk=1wkn=0nk=1wkn=0

证明: nk=1ωkn=ωn(ωnn1)ωn1=0nk=1ωkn=ωn(ωnn1)ωn1=0

离散傅里叶变换(DFT)

即将一个多项式转换为用表达单位复数根的形式,即
yk=A(ωkn)=nj=0ajωkjnyk=A(ωkn)=nj=0ajωkjn
两个多项式转换为点值表达之后,我们就可以O(n)O(n)对两个式子相乘

离散傅里叶逆变换(DFT)

这里本来有一个通过矩阵解释的绝妙的证明过程,但矩阵在Markdown中实在太难打了,所以很遗憾
ak=nj=0yjωkjnak=nj=0yjωkjn
这样我们就完成了多项式O(nlogn)O(nlogn)相乘

具体实现代码

inline void Dft(comp *a,int len,int rev)
{
    if(len==1)return;
    for(register int i=0;i < len;++i)temp[i]=a[i];
    for(register int i=0;i<len;++i)
        if(i&1)
            a[len/2+i/2]=temp[i];
        else
            a[i/2]=temp[i];
    comp *g=a,*h=a+len/2;
    Dft(g,len/2,rev),Dft(h,len/2,rev);
    comp w(1,0),step(cos(2.0*pi/len),rev*sin(2.0*pi/len));
    for(register int k=0;k<len/2;++k)
    {
        comp t=w*h[k];
        temp[k]=g[k]+t;
        temp[k+len/2]=g[k]-t;
        w*=step;
    }
    for(register int i=0;i<len;++i)a[i]=temp[i];
}

其中FFT有一个叫做蝴蝶变换的优化,大致即为将递归到最后一层时的得到的新数组序列预先处理好,就是将原数组排序为递归时得到的数组,再一层一层往上更新即可

inline void change(comp *a,int len)
{
    for(register int i=0;i<len;++i)
    {
        re[i]=re[i>>1]>>1;
        if(i&1)
            re[i]|=len>>1;
    }
    for(register int i=0;i<len;++FFTi)
        if(i<re[i])swap(a[i],a[re[i]]);
}

inline void Dft(comp *a,int len,int rev)
{
    change(a,len);
    for(register int h=2;h<=len;h<<=1)
    {
        comp step(cos(2.0*pi/h),rev*sin(2.0*pi/h));
        for(register int j=0;j<len;j+=h)
        {
            comp w(1,0);
            for(register int k=j;k<j+h/2;++k,w*=step)
            {
                comp u=a[k];
                comp t=a[k+h/2]*w;
                a[k]=u+t;
                a[k+h/2]=u-t;
            }
        }
    }
}

快速傅里叶逆变换

前置芝士:逆元,NTT


逆元

即满足 ax1modpax1modp 其中 a,pa,p 互质,xx 即被称作 aa 关于 pp 的逆元

求解逆元: 一般用 exgedexged 或者 费马小定理

费马小定理求解逆元:

因为 ap11modpap11modp

所以 aap21modpaap21modp

得出 aa 关于 pp 的逆元即为 ap2ap2

快速数论变换(NTT)

关于NTT,我们很明显的看出它与FFT之间的关系(通过中文,它可以只在整数域内变换,完成nlognnlogn多项式乘,具体实现我们使用原根 gg 代替了单位复数根 ωω ,一般来说,NTT常用模数为998244353或者1004535809,且它们的原根都为3

这里的代替即我们让 g(p1)/ng(p1)/n 等价于 e2πi/ne2πi/n并在运算的时候对模数取模运算即可

由于是整数运算,所以正常情况下要比复数运算的FFT快不少

非正常情况:有关字符串匹配的问题上,NTT要比FFT慢许多


文章作者: Caicz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Caicz !
评论

Related Issues not found

Please contact @Caicz to initialize the comment