【Uoj34】多项式乘法(NTT,FFT)

题面

uoj

题解

首先多项式乘法用 FFT 是一个很久很久以前就写过的东西
直接贴一下代码吧。。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<complex>
using namespace std;
#define ll long long
#define RG register
#define MAX 300000
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
const double Pi=acos(-1);
complex<double> a[MAX],b[MAX];
int r[MAX],n,m,l;
void FFT(complex<double> *P,int opt)
{
    for(int i=0;i<n;++i)if(i<r[i])swap(P[i],P[r[i]]);
    for(int i=1;i<n;i<<=1)
    {
        complex<double> W(cos(Pi/i),opt*sin(Pi/i));
        for(int p=i<<1,j=0;j<n;j+=p)
        {
            complex<double> w(1,0);
            for(int k=0;k<i;w*=W,++k)
            {
                complex<double> X=P[j+k],Y=w*P[i+j+k];
                P[j+k]=X+Y;P[i+j+k]=X-Y;
            }
        }
    }
}
int 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();
    m+=n;
    for(n=1;n<=m;n<<=1)++l;
    for(int i=0;i<n;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    FFT(a,1);FFT(b,1);
    for(int i=0;i<n;++i)a[i]*=b[i];
    FFT(a,-1);
    for(int i=0;i<=m;++i)printf("%d ",(int)(a[i].real()/n+0.5));
    puts("");
    return 0;
}

我们知道 FFT 中使用单位复根
满足两个引理

(Wk2n)2=Wkn

Wkn=Wk+n/2n

单位复根在算的过程中很容易出现精度的问题
现在要找到一个拥有相同性质的东西能够代替单位复根就好了

主要是第二个性质难找
因为 Wn n 次单位复根
所以:(Wn)n=1,(Wn)n/2=1

其实,这个性质可以被原根满足:
假设 p 的原根是g
再膜 p 意义下:
gφ(p)=1gφ(p)/2=1
因为原根不存在一个比 φ(p) 小的数使得 gk=1
所以 gφ(p)/2=1
我们发现上面的性质也可以满足

所以,把 n 次单位复根可以替换成原根的φ(p)/(2n)来做
这样就解决了小数精度的问题
当然也是用来解决卷积取膜的问题

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 3000000
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
const int pr=3;
const int MOD=998244353;
const int phi=MOD-1;
int n,m,r[MAX],l;
int a[MAX],b[MAX];
int fpow(int a,int b)
{
    int s=1;
    while(b){if(b&1)s=1ll*s*a%MOD;a=1ll*a*a%MOD;b>>=1;}
    return s;
}
void NTT(int *P,int opt)
{
    for(int i=0;i<n;++i)if(i<r[i])swap(P[i],P[r[i]]);
    for(int i=1;i<n;i<<=1)
    {
        int W=fpow(pr,phi/(i<<1));
        for(int p=i<<1,j=0;j<n;j+=p)
        {
            int w=1;
            for(int k=0;k<i;++k,w=1ll*w*W%MOD)
            {
                int X=P[j+k],Y=1ll*w*P[i+j+k]%MOD;
                P[j+k]=(X+Y)%MOD;P[i+j+k]=(X-Y+MOD)%MOD;
            }
        }
    }
    if(opt==-1)reverse(&P[1],&P[n]);
}
int 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();
    m+=n;
    for(n=1;n<=m;n<<=1)++l;
    for(int i=0;i<n;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    NTT(a,1);NTT(b,1);
    for(int i=0;i<n;++i)a[i]=1ll*a[i]*b[i]%MOD;
    NTT(a,-1);
    int inv=fpow(n,MOD-2);
    for(int i=0;i<n;++i)a[i]=1ll*a[i]*inv%MOD;
    for(int i=0;i<=m;++i)printf("%d ",a[i]);puts("");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值