FFT

【算法简介】

FFT主要用于处理多项式乘法的问题,时间复杂度可以优化到O(NlogN)

整体思路是把两个多项式转换为点值表达,然后用O(N)进行乘法

这个问题的瓶颈就在于如何用更快的方式转换为点值

 

这里就要引入傅里叶的方法了:

我们取一些特殊的点计算点值,也就是把复数的单位圆平均分成n分的值带入

然后我们再针对这个进行加速,也就是快速傅里叶变换

这样我们就可以证明在N*logN的复杂度进行一些操作

 

继续优化:把数组分到最后的位置,我们可以发现一些规律

如果一个点位置在i,那么它最后的位置就在它二进制的取反的位置

比如

6(110)-> 3(011)

利用这个性质就可以的到一个非递归的版本

 

继续优化, 下面就是优美的蝴蝶变换了

其实就是优化掉了buf数组      名字好高级

 

剩下的就是代码

【代码】

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define mp make_pair
#define fi first
#define se second
#define lson now<<1
#define rson now<<1|1
typedef long long ll;
const double PI=acos(-1.0);
const int maxn=4e6+5;
int n,m;
struct complex
{
    double x,y;
    complex (double xx=0,double yy=0)
    {
        x=xx; y=yy;
    }
}a[maxn],b[maxn];
complex operator + (complex a,complex b)
{
    return complex(a.x+b.x,a.y+b.y);
}
complex operator - (complex a,complex b)
{
    return complex(a.x-b.x,a.y-b.y);
}
complex operator * (complex a,complex b)
{
    return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);
}
int lim=1,l,r[maxn];
void fft(complex *A,int op)
{
    for(int i=0;i<lim;i++)
        if(i<r[i]) swap(A[i],A[r[i]]);
    for(int mid=1;mid<lim;mid<<=1)
    {
        complex Wn(cos(PI/mid),op*sin(PI/mid));
        for(int R=mid<<1,j=0;j<lim;j+=R)
        {
            complex w(1,0);
            for(int k=0;k<mid;k++,w=w*Wn)
            {
                complex x=A[j+k],y=w*A[j+mid+k];
                A[j+k]=x+y;
                A[j+mid+k]=x-y;
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++) scanf("%lf",&a[i].x);
    for(int j=0;j<=m;j++) scanf("%lf",&b[j].x);
    while(lim<=(n+m)) lim<<=1,l++;
    for(int i=0;i<lim;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    fft(a,1); fft(b,1);
    for(int i=0;i<=lim;i++) a[i]=a[i]*b[i];
    fft(a,-1);
    for(int i=0;i<=n+m;i++)
        printf("%d ",(int)(a[i].x/lim+0.5));
    return 0;
}

 

【例题】P1919 【模板】A*B Problem升级版(FFT快速傅里叶)

sol.

 

 

NTT

考虑由于单位根的选取,一定会产生精度的问题,所以取模的操作很难进行,且速度较慢

所以NTT应运而生,来解决取模的问题

我们利用原根的性质来构造单位根

设原根为g,可以证明g^{\frac{p-1}{n}}是一个合理的方案

常见原根 : 998244353  ——> 3

 

......

这样我们就得到了NTT的板子

void ntt(int *A,int op)
{
    for(int i=0;i<lim;i++)
        if(r[i]<i) swap(A[i],A[r[i]]);
    for(int i=1;i<lim;i<<=1)
    {
        ll Wn;
        if(op==1) Wn=qpow(3,(mod-1)/(i<<1));
        else Wn=qpow((mod+1)/3,(mod-1)/(i<<1));
        for(int R=i<<1,j=0;j<lim;j+=R)
        {
            ll w=1LL;
            for(int k=0;k<i;k++,w=w*Wn%mod)
            {
                int x=A[j+k],y=1LL*w*A[j+k+i]%mod;
                A[j+k]=(x+y)%mod;
                A[j+k+i]=(x-y+mod)%mod;
            }
        }
    }
    if(op==-1)
    {
        int inv=qpow(lim,mod-2);
        for(int i=0;i<lim;i++) A[i]=1LL*A[i]*inv%mod;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值