FFT & NTT学习心得

基础知识浅谈

FFT—快速傅里叶变换

基本功能

在O( (n+m)log(n+m) )的时间复杂度内计算:n次多项式乘m次多项式.

实现方式

欲求多项式A*多项式B:
1.对多项式A,B分别进行快速傅里叶变换,得到A1,B1;
2.将A1,B1的对应项相乘得到多项式C1.即:C1[i]=A1[i]*B1[i] 其中C1[i]表示C1的第i项系数.
3.将多项式C1进行逆变换得到多项式C.则多项式C即是多项式A与多项式B相乘的结果.
具体证明这里不多说了.

NTT—快速数论变换

基本功能

与FFT类似,但没有精度差.

实现方式

与FFT类似,只是用数论原根的幂代替FFT中的w[n].

原根的含义及求法

根据欧拉定理,若a,p互质则有:

aϕ(p)1  (mod  p)

如果当且仅当 x=ϕ(p)
ax1  (mod  p)

则a是p的一个原根.
用更专业的语言来描述即:a模p的阶等于 ϕ(p) 则a是p的原根.
原根的求法:没有什么特别的方法,只能暴力枚举,验证时略有技巧,这里就不细说了.

关于NTT算法的一些说明

优点

相比起FFT来说,NTT最显著的优势在于没有精度差.由于FFT会用到complex double ,在大数据下不排除出现精度差的可能.在某些评测机上,效率可能不如NTT.(NTT虽然不用复数运算,但是取模很多).

限制

相比起FFT来说,NTT的限制很多.

  • 所求的多项式要求是整系数
  • 如果题目要求结果对质数p取模,这个质数往往只能是998244353,否则会有很多麻烦,这个会在后面谈到.
  • 所求多项式的项数应在 223 之内,因为 998244353=717223+1
  • 结果的系数不应超过质数P.(P是自己选择的质数,一般定为P=998244353)

具体代码实现

FFT模板

const double pi=3.1415926535897932384626433832795;
complex<double>Wi[MAXN];
char s[MAXN],t[MAXN];
void FFT(complex<double> A[],int nn,int ty)
{
    int i,j,k,m;
    complex<double> t0,t1;
    for(i=0;i<nn;i++)
    {
        for(j=0,k=i,m=1;m<nn;m<<=1,j=(j<<1)|(k&1),k>>=1);
        if(i<j)t0=A[i],A[i]=A[j],A[j]=t0;
    }//这段for循环不建议擅自改动,极易出错.
    Wi[0]=1;
    for(m=1;m<nn;m<<=1)
    {
        t0=exp(complex<double>(0,ty*pi/m));
        for(i=1;i<m;i++)
            Wi[i]=Wi[i-1]*t0;
        for(k=0;k<nn;k+=m<<1)
            for(i=k;i<k+m;i++)
            {
                t0=A[i];
                t1=A[i+m]*Wi[i-k];
                A[i]=t0+t1;
                A[i+m]=t0-t1;
            }
    }
    if(ty==1)
        return;//ty==-1时为逆变换.
    t0=1.0/nn;
    for(i=0;i<nn;i++)
        A[i]*=t0;
}

NTT模板

const int P=998244353;
const int g=3;//P的原根
int W[MAXN];
int exp(int a,int k)
{
    ll A=1LL*a,ANS=1LL;
    for(;k;k>>=1,A=A*A%P)
    {
        if(k&1)
        {
            ANS=ANS*A%P;
        }
    }
    return (int)ANS%P;
}//其实这里的函数名取为exp是不恰当的,不过无伤大雅.
void NTT(int A[],int nn,int ty)
{
    int t1,t2,i,j,k,m;
    for(i=0;i<nn;i++)
    {
        for(j=0,k=i,m=1;m<nn;m<<=1,j=(j<<1)|(k&1),k>>=1);
        if(i<j)
        {
            t1=A[i];
            A[i]=A[j];
            A[j]=t1;
        }
    }
    W[0]=1;
    for(m=1;m<nn;m<<=1)
    {
        t1=exp(g,P-1+ty*(P-1)/(m<<1));
        for(i=1;i<m;i++)
        {
            W[i]=1LL*W[i-1]*t1%P;
        }
        for(k=0;k<nn;k+=m<<1)
        {
            for(i=k;i<k+m;i++)
            {
                t1=A[i];
                t2=1LL*A[i+m]*W[i-k]%P;
                A[i]=t1+t2;
                A[i]-=A[i]>P?P:0;
                A[i+m]=t1-t2;
                A[i+m]+=A[i+m]<0?P:0;
            }
        }
    }
    if(ty==1)
    {
        return ;
    }
    t1=exp(nn,P-2);
    for(i=0;i<nn;i++)
    {
        A[i]=1LL*A[i]*t1%P;
    }
    return ;
}

FFT及NTT的简单应用

最基本应用–高精度乘法

将十进制数看成一个多项式,然后利用FFT或NTT求解.(注意进位).
比如:
123*456=56088可以看成:

123=1102+2101+3100

456=4102+5101+6100

123456=4104+13103+28102+27101+18100

考虑进位则有:
123456=5104+6103+0102+8101+8100=56088

稍微高端一点的应用

特殊的计数问题

题目大意

给出两个长度分别为n,m的01序列A、B,有Q次询问,每次询问要求回答:当把A的第i位和B的第j位对齐时,A,B公共部分有多少对对齐且相同的数.
如:
输入n,m,A串,B串,Q,每次询问(i,j)
6 6
000111
111100
3
1 1
1 2
4 2
输出每次询问的答案:
1
0
3

数据范围:

n<=100000,Q<=1000000

解题方法

注意到所有询问总共只有2n-1种本质不同的询问,我们希望预处理出所有这些本质不同的询问的答案.
比如询问为(i,j)我们可以等价转化为(1,j-i),因此我们不妨将询问设为(1,k)
我们先考虑有多少位上的1是对齐的:
其实两个位置上的1对齐,当且仅当两个位置都是1,且两个位置相差k-1.
如果将其中一个序列倒序排列的话,那么两个之前对齐的位置现在的位置标号之和就是定值了.于是就可以构造多项式进行计数.
这之后,我们将所有0,1取反,就可以同样计算出有多少位0是对齐的.

参考代码
#include<cstdio>
  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值