基础知识浅谈
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互质则有:
如果当且仅当 x=ϕ(p) 时
则a是p的一个原根.
用更专业的语言来描述即:a模p的阶等于 ϕ(p) 则a是p的原根.
原根的求法:没有什么特别的方法,只能暴力枚举,验证时略有技巧,这里就不细说了.
关于NTT算法的一些说明
优点
相比起FFT来说,NTT最显著的优势在于没有精度差.由于FFT会用到complex double ,在大数据下不排除出现精度差的可能.在某些评测机上,效率可能不如NTT.(NTT虽然不用复数运算,但是取模很多).
限制
相比起FFT来说,NTT的限制很多.
- 所求的多项式要求是整系数
- 如果题目要求结果对质数p取模,这个质数往往只能是998244353,否则会有很多麻烦,这个会在后面谈到.
- 所求多项式的项数应在 223 之内,因为 998244353=7∗17∗223+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可以看成:
考虑进位则有:
稍微高端一点的应用
特殊的计数问题
题目大意
给出两个长度分别为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>