快速数论变换(NTT)

Deadly 提交于 2019-12-14 12:32:10

前置芝士:快速傅立叶变换(FFT)

垃圾\(FFT\)

我们已经知道\(FFT\)这个东西他很\(nb\)了,但是仍然存在很多局限性

比如说

\(1.\)要用到复数,所以它很慢
\(2.\)要用到复数,所以是\(double\)类型,有精度误差
\(3.\)要用到复数,不能取模
\(……\)

我们发现就是这个垃圾复数拖了算法后退,要想办法摆脱它(´༎ຶД༎ຶ`)!

原根

聪明的珂学家们发现了一种奇妙的东西:原根

它可以代替复数进行变换,而且具有优良的性质

\(gcd(a,p)==1\),且\(p>1\)

则对于\(a^x≡1(mod\ p)\)最小的\(x\),我们称为\(a\)\(p\)的阶,记作\(\delta _p(a)\)

相关定理

定理一:若\(p>1\)\(gcd(a,p)==1\)\(a^n≡1(mod\ p)\),则\(\delta _p(a)|n\)

证明:根据定义理解一下吧……

定理二\(\delta _p(a)|\phi(m)\)

证明:由欧拉定理:\(a^{\phi(p)}≡1(mod\ p)\),所以\(\delta _p(a)≤\phi(p)\),根据定理一得到\(\delta _p(a)|\phi(m)\)

原根

\(\delta _p(a)=\phi(p)\),则称\(a\)为模\(p\)的一个原根

\(\delta _9(2)=6=\phi(9)\),称\(2\)为模\(9\)的一个原根

相关定理

定理一: 一个正整数\(p\)存在原根,当且仅当\(p=2,4,p^s,2p^s\),其中\(p\)为奇素数,\(s\)为正整数

定理二: 一个数\(p\)如果存在原根,则原根个数为\(\phi(\phi(p))\)

定理三: 若\(p\)是一个素数,\(g\)\(p\)的一个原根,那么\(g^i\ mod\ p (1<g<p,0<i<p)\)的结果互不相同

证明咱也不会嘛……\(qwq\)

一些性质

回想一下为什么我们要用单位根作为系数带入多项式?是因为它具有很多优良性质,我们康康这些性质原根有没有!

我们假设一个前提\(p=a*2^k+1\),其中\(k\)很大,沿用\(fft\)的方法,只考虑\(n\)\(2\)的整次幂的情况

\(\omega _n^1=g^{\frac{p-1}{n}}\)

首先可以确保\(\omega _n^1\)不再是恶心的复数,因为\(p=a*2^k+1\),所以\(\frac{p-1}{n}\)一定是整数

性质一\(\omega_ n^k(0\le k \le n-1)\)互不相同

证明:根据原根的定理三可知

性质二\(\omega _{2n}^{2k} = \omega _n^k\)

证明:\(\omega _{2n}^{2k}=(g^{\frac{2k(p-1)}{2n}})=g^{\frac{k(p-1)}{n}}=\omega _n^k\)

性质三\(\omega _{n}^{k+\frac{n}{2}} = -\omega _n^k\)

证明:\(\omega _{n}^{k+\frac{n}{2}} = \omega _n^k *\omega _n^{\frac{n}{2}}\)

因为\((\omega _n^{\frac{n}{2}})^2=\omega _n^n=1\)

所以\(\omega _n^{\frac{n}{2}}=1\)\(-1\)

又因为\(\omega _n^{\frac{n}{2}}与\omega _n^0\)不同

所以\(\omega _n^{\frac{n}{2}}=-1\)

所以\(\omega _{n}^{k+\frac{n}{2}} = -\omega _n^k\)

性质四\(\sum\limits_{i=0}^{n-1}\omega_ n^i=0\)

证明:和单位根一模一样,懒得打了……

性质五\(\omega _n^k=\omega _n^{n\%k}\)

证明:\(\omega _n^k=g^{\frac{k(p-1)}{n}}\)

由费马小定理可知,当\(p\)为质数时,\(a^(p-1)\equiv1(mod\ p)\)

所以\(\omega _n^k=g^{\frac{k(p-1)}{n}\% (p-1)}\)

考虑留下的应该是\(\frac{n}{k}\)的小数部分\(*(p-1)\),得到\(\omega _n^k=g^{\frac{k\%n(p-1)}{n}}=\omega _n^{k\%n}\)

我们发现傅立叶变换中需要用到的单位根的性质原根统统满足!那我们还要垃圾单位根干啥!直接上原根啊!

不过也有一定的限制,就在于模数\(p\)必须满足\(p=a*2^k+1\),且不能包含小数

怎么找原根?

暴力枚举(雾

由于一个质数原根有\(\phi(p-1)\)个,最小的原根一般很小

枚举\(g\)\(k\)复杂度约为\(O(plogp)\)

但是跟具阶的定理二,我们发现如果存在其他\(k\ne \phi(p)\)使得\(g^k\equiv 1(mod\ p)\),那么\(k\)一定是\(\phi(p)\)的约数,所以其实只要枚举\(\phi(p)\)的约数即可

复杂度期望\(O(log^2p)\)

\(code\)

然后我们快来用原根草多项式吧!

#include<bits/stdc++.h>
using namespace std;
namespace red{
#define int long long
#define eps (1e-8)
    inline int read()
    {
        int x=0;char ch,f=1;
        for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
        if(ch=='-') f=0,ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
        return f?x:-x;
    }
    const int N=5e6+10,p=998244353,g=3,gi=332748118;
    int n,m;
    int a[N],b[N];
    int limit,len;
    int pos[N];
    inline int fast(int x,int k)
    {
        int ret=1;
        while(k)
        {
            if(k&1) ret=ret*x%p;
            x=x*x%p;
            k>>=1;
        }
        return ret;
    }
    inline void ntt(int *a,int inv)
    {
        for(int i=0;i<limit;++i)
            if(i<pos[i]) swap(a[i],a[pos[i]]);
        for(int mid=1;mid<limit;mid<<=1)
        {
            int Wn=fast(inv?g:gi,(p-1)/(mid<<1));
            for(int r=mid<<1,j=0;j<limit;j+=r)
            {
                int w=1;
                for(int k=0;k<mid;++k,w=w*Wn%p)
                {
                    int x=a[j+k],y=w*a[j+k+mid]%p;
                    a[j+k]=(x+y)%p;
                    a[j+k+mid]=(x-y)%p;
                    if(a[j+k+mid]<0) a[j+k+mid]+=p;
                }
            }
        }
    }
    inline void main()
    {
        n=read(),m=read();
        for(int i=0;i<=n;++i) a[i]=read();
        for(int i=0;i<=m;++i) b[i]=read();
        for(limit=1;limit<=n+m;limit<<=1) ++len;
        for(int i=0;i<limit;++i) pos[i]=(pos[i>>1]>>1)|((i&1)<<(len-1));
        ntt(a,1);ntt(b,1);
        for(int i=0;i<limit;++i) a[i]=a[i]*b[i]%p;
        ntt(a,0);
        int inv=fast(limit,p-2);
        for(int i=0;i<=n+m;++i) printf("%lld ",a[i]*inv%p);
    }
}
signed main()
{
    red::main();
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!