前言
在这篇文章中我介绍了什么是 \(\text{FFT}\) ,但是在文中我们也说了一嘴这玩意是有精度误差的,三角函数和复数导致我们不得不进行取整操作。
只要毒瘤出题人原因,那就可以 \(\text{Hack FFT}\)。
Tips:
根据《NOI大纲》的内容,卡精度和卡常数通常是不允许的。
所以 \(\text{FFT}\) 通常不会寄,不必过度焦虑。
但是 \(\text{NTT}\) 本身并不难。
这时候我们就需要引入快速数论变换 \(\text{NTT}\)(\(\text{Fast Number Theory Transform}\),不过我们通常省略那个 \(\text F\))。
首先我们要明确一个方向,就是 \(\text{FFT}\) 的原理是单位根的几个性质:
- 消去原理: \(\omega_{tn}^{tk}=\omega_{n}^k\)
- 对称原理:\(\omega_{n}^{k}=-\omega_n^{k+\frac n 2}\)
- \(\omega_{n}^k=(\omega_n^1)^k\)
- \(\omega_n^i\not=\omega_n^j(i\not=j)\)
也就是说,只要满足以上条件,也就可以用类似的方法实现。
我们发现,数论里的原根,和这个很像啊!所以直接使用即可。
代码实现
经典的模数 \(998244353\) 的原根为 \(3\),这是常用的组合。
直接替换 \(\omega\) 即可。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL N=1e6;
const LL mod=998244353;
const LL G=3;
LL ksm(LL x,LL y)
{
LL ans=1;
while(y)
{
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
LL n,m,Gi,lim,len=1,r[N],x,ans[N];
LL a[N],b[N];
void FFT(LL *a,LL inv)
{
for(int i=0;i<=len-1;i++)
{
if(i<r[i])swap(a[i],a[r[i]]);
}
for(int l=2;l<=len;l*=2)
{
LL omg=ksm(G,(mod-1)/l);
if(inv)omg=ksm(Gi,(mod-1)/l);
LL m=l/2;
for(int j=0;j<=len-1;j+=l)
{
LL x=1;
for(int i=0;i<=m-1;i++)
{
LL t=x;
t=a[i+j+m]*t%mod;
a[i+j+m]=(a[i+j]-t+mod)%mod,a[i+j]=(a[i+j]+t)%mod;
x=x*omg%mod;
}
}
}
}
int main()
{
scanf("%lld%lld",&n,&m);
Gi=ksm(G,mod-2);
while(len<=n+m)len*=2,lim++;
for(int i=0;i<=len-1;i++)
{
LL sum=0;
for(int j=0;j<=lim-1;j++)sum|=((i>>j)&1)<<(lim-j-1);
r[i]=sum;
}
for(int i=0;i<=n;i++)
{
scanf("%lld",&a[i]);
}
for(int i=0;i<=m;i++)
{
scanf("%lld",&b[i]);
}
FFT(a,0),FFT(b,0);
for(int i=0;i<=len-1;i++)
{
a[i]=a[i]*b[i]%mod;
}
FFT(a,1);
LL inv=ksm(len,mod-2);
for(int i=0;i<=n+m;i++)
{
ans[i]=a[i]*inv%mod;
printf("%lld ",ans[i]);
}
return 0;
}
扩展
通常情况下在模意义都会使用原根来替换单位根,比如单位根反演就可以使用原根。
这里简单展示一下单位根反演的结论:
\[[n|k]=\frac 1 n \sum_{i=0}^{n-1}\omega_{n}^{ki} \] 标签:数论,text,LL,FFT,单位根,NTT,笔记,ans,omega From: https://www.cnblogs.com/dengduck/p/NTT.html