洛谷P3803 fft模板

本文介绍了一种通过单位根将多项式系数向量高效转换为点值表示法的方法,以提高乘法运算效率。通过将系数按奇偶拆分并利用单位根的周期性,将计算复杂度降低至线性。关键步骤包括利用FFT进行快速计算和合并,适用于2的幂次规模。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概念:

先引入多项式的两个表示方法:

1.1.1.系数表示法,唯一确定一个nnn次的多项式需要每一项的系数,从低次项到高次项依次写成向量的形式,这个向量就能唯一确定一个多项式。即向量r=[a0,a1,.....,an]r=[a_{0},a_{1},.....,a_{n}]r=[a0,a1,.....,an],就能唯一确定多项式f(x)=a0+a1x+a2x2+...+anxnf(x)=a_{0}+a_{1}x+a_{2}x^{2}+...+a_{n}x^{n}f(x)=a0+a1x+a2x2+...+anxn

此时计算一次f(x)f(x)f(x)的时间复杂度是O(n)O(n)O(n)。多项式相加结果即向量相加,复杂度为O(n)O(n)O(n),多项式相乘即向量相乘,时间复杂度是O(n2)O(n^{2})O(n2)

等长度向量相乘的方法:

设向量x=[a0,a1,...,an]x=[a_{0},a_{1},...,a_{n}]x=[a0,a1,...,an]与向量y=[b0,b1,...,bn]y=[b_{0},b_{1},...,b_{n}]y=[b0,b1,...,bn]相乘,得到的还是长度为nnn的向量z=[c0,c1,...,cn]z=[c_{0},c_{1},...,c_{n}]z=[c0,c1,...,cn]ckc_{k}ckx,yx,yx,y中所有下标之和为kkk的对的积的和,即ck=∑i=0kaibk−ic_{k}=\sum_{i=0}^{k}a_{i}b_{k-i}ck=i=0kaibki

2.2.2.点值表示法,唯一确定一个nnn次的多项式需要n+1n+1n+1个点(可以理解为解n+1n+1n+1元方程需要n+1n+1n+1个方程,每一个点可以确定所有系数的一组关系,有n+1n+1n+1个系数,就是n+1n+1n+1元方程)。点是二维的,所以需要一对列表(此处非向量)来表示这个多项式,即[x1,x2,...,xn][x_{1},x_{2},...,x_{n}][x1,x2,...,xn],与[y1,y2,...,yn][y_{1},y_{2},...,y_{n}][y1,y2,...,yn]就可以唯一确定。此时多项式相加依然满足yyy直接相加,乘法直接相乘即可。此时多项式加减法的时间复杂度都是O(n)O(n)O(n)

单位根的概念:

满足方程zn=1z^{n}=1zn=1的复数解zzznnn次单位根。

性质111:单位根的模一定是111,并且nθ=2kπn\theta=2k\pinθ=2

由欧拉定理eiθ=cosθ+isinθe^{i\theta}=cos\theta+isin\thetaeiθ=cosθ+isinθ可知,任意一个右式形式的复数都可以被写成左式的形式,同时注意到,右式的模cos2θ+sin2θ=1\sqrt{cos^{2}\theta+sin^{2}\theta=1}cos2θ+sin2θ=1,这是一个长度为111的向量的形式,更一般的,假设长度为rrr,那么形式应是reiθ=rcosθ+risinθre^{i\theta}=rcos\theta+risin\thetareiθ=rcosθ+risinθ的形式(在复平面,rrr即是向量的模,θ\thetaθ是向量的辐角)。因此,设z=reiθ=rcosθ+risinθ⇒zn=rneinθ=rncosnθ+rnisinnθ=1⇒rn(cosnθ+isinnθ)=1z=re^{i\theta}=rcos\theta+risin\theta\Rightarrow z^{n}=r^{n}e^{in\theta}=r^{n}cosn\theta+r^{n}isin n\theta=1 \Rightarrow r^{n}(cosn\theta+isin n\theta)=1z=reiθ=rcosθ+risinθzn=rneinθ=rncosnθ+rnisinnθ=1rn(cosnθ+isinnθ)=1,右式不存在有虚数,因此sinnnθ=0⇒nθ=2kπsin^{n}n\theta=0 \Rightarrow n\theta=2k\pisinnnθ=0nθ=2,代入有rn=1⇒r=1r^{n}=1\Rightarrow r=1rn=1r=1

性质2:n2:n2n次单位根有nnn个,并且第kkk个单位根写作wnkw_{n}^{k}wnk,有wnk=re2πknw_{n}^{k}=re^{2\pi\frac{k}{n}}wnk=re2πnk,单位根是从000标号到n−1n-1n1的。

注意到每个单位根与自己相乘就是让自己的辐角翻倍,因此,单位根是逆时针顺序在单位圆上排序的,间隔角度是2π/n2\pi/n2π/n,那么第kkk个就是原命题的形式了。

性质333wdndk=wnkw_{dn}^{dk}=w_{n}^{k}wdndk=wnkwnk+n2=wn2kw_{n}^{k+\frac{n}{2}}=w_{\frac{n}{2}}^{k}wnk+2n=w2nk

展开计算,上面的性质是容易得到的。

算法:已知两多项式系数,求两多项式的系数向量乘积

已知多项式的系数向量分别为:x=[a0,a1,...,an]x=[a_{0},a_{1},...,a_{n}]x=[a0,a1,...,an]y=[b0,b1,...,bn]y=[b_{0},b_{1},...,b_{n}]y=[b0,b1,...,bn],求乘积向量,其中ci=∑j=0iajbi−jc_{i}=\sum_{j=0}^{i}a_{j}b_{i-j}ci=j=0iajbij,时间复杂度是O(n2)O(n^2)O(n2),难以接受。注意到点值式的乘积运算时O(n)O(n)O(n)的,那么可以利用点值式来计算,现在的问题是,系数如何转换为点值,也就是该用拿几个点?求出点值合并完成后如何转换为系数?并且计算一次值的时间复杂度是O(n)O(n)O(n),计算nnn项的时间复杂度同样是O(n2)O(n^2)O(n2),该怎么加快计算点值?

上述几个问题能被单位根完美解决,我们选用的点值就是单位根上的点值。

设原来的向量为A=[a0,a1,...,an]A=[a_{0},a_{1},...,a_{n}]A=[a0,a1,...,an]

我们把系数按奇偶次序分开两个向量,得到:

A[0]=[a0,a2,a4....]A^{[0]}=[a_{0},a_{2},a_{4}....]A[0]=[a0,a2,a4....]

A[1]=[a1,a3,a5....]A^{[1]}=[a_{1},a_{3},a_{5}....]A[1]=[a1,a3,a5....]

对应的多项式是:

f(x)=a0+a1x+a2x2+a3x3+....f(x)=a_{0}+a_{1}x+a_{2}x^{2}+a_{3}x^{3}+....f(x)=a0+a1x+a2x2+a3x3+....

f[0](x)=a0+a2x+a4x2+....f^{[0]}(x)=a_{0}+a_{2}x+a_{4}x^{2}+....f[0](x)=a0+a2x+a4x2+....

f[1](x)=a1+a3x+a5x2+....f^{[1]}(x)=a_{1}+a_{3}x+a_{5}x^{2}+....f[1](x)=a1+a3x+a5x2+....

把两个形式变换得到:

f(x)=f[0](x2)+xf[1](x2)f(x)=f^{[0]}(x^{2})+xf^{[1]}(x^{2})f(x)=f[0](x2)+xf[1](x2)

假设我们要计算单位根wnkw_{n}^{k}wnk

带入得到:

f(wnk)=f[0]((wnk)2)+wnkf[1]((wnk)2)f(w_{n}^{k})=f^{[0]}((w_{n}^{k})^{2})+w_{n}^{k}f^{[1]}((w_{n}^{k})^{2})f(wnk)=f[0]((wnk)2)+wnkf[1]((wnk)2)

化简得到:

f(wnk)=f[0](wn2k)+wnkf[1](wn2k)f(w_{n}^{k})=f^{[0]}(w_{\frac{n}{2}}^{k})+w_{n}^{k}f^{[1]}(w_{\frac{n}{2}}^{k})f(wnk)=f[0](w2nk)+wnkf[1](w2nk)

注意到单位根的性质222,我们求的如果是wnk+n2w_{n}^{k+\frac{n}{2}}wnk+2n,最后会是

f(wnk+n2)=f[0](wn2k)−wnkf[1](wn2k)f(w_{n}^{k+\frac{n}{2}})=f^{[0]}(w_{\frac{n}{2}}^{k})-w_{n}^{k}f^{[1]}(w_{\frac{n}{2}}^{k})f(wnk+2n)=f[0](w2nk)wnkf[1](w2nk)

因此,我们只需要计算出前一半的单位根对应的f[0]f^{[0]}f[0]f[1]f^{[1]}f[1],后一半是可以O(1)O(1)O(1)得到的。

同时前式和后式是一样的问题,于是可以分治解决,最后合并。

最后给出点值转换回系数的方法:

ai=cina_{i}=\frac{c_{i}}{n}ai=nci

由于fftfftfft使用doubledoubledouble保存复数向量,所以最后要四舍五入,这里也给出一种四舍五入的方式:(int)(a+0.5)(int)(a+0.5)(int)(a+0.5)

由于算法基于对称的方法,所以只适合规模是222的幂次的情形,因此,如果规模不为222的幂次,需要添加额外项使规模是222的幂,添加的额外项系数是0。

#include<bits/stdc++.h>
#define ll long long
#define complex Complex
using namespace std;

int read()
{
    int ret=0,base=1;
    char ch=getchar();
    while(!isdigit(ch))
    {
        if(ch=='-') base=-1;
        ch=getchar();
    }
    while(isdigit(ch))
    {
        ret=(ret<<3)+(ret<<1)+ch-48;
        ch=getchar();
    }
    return ret*base;
}

const double pi=acos(-1);

struct complex
{
    double a,b;
    complex(){a=0;b=0;}
    complex(double x,double y){a=x;b=y;}
    complex operator+(complex x){return complex(a+x.a,b+x.b);}
    complex operator-(complex x){return complex(a-x.a,b-x.b);}
    complex operator*(complex x){return complex(a*x.a-b*x.b,a*x.b+b*x.a);}
};

void fft(vector<complex>&a,int limit,int op)
{
    if(limit==1) return;
    vector<complex>ret1(limit>>1),ret2(limit>>1);
    for(int i=0;i<limit;i+=2) ret1[i>>1]=a[i],ret2[i>>1]=a[i+1];
    fft(ret1,limit>>1,op);fft(ret2,limit>>1,op);
    complex base=complex(cos(2.0*pi/limit),op*sin(2.0*pi/limit)),now=complex(1,0);
    for(int i=0;i<(limit>>1);i++)
    {
        a[i]=ret1[i]+now*ret2[i];
        a[i+(limit>>1)]=ret1[i]-now*ret2[i];
        now=now*base;
    }
}

int n,m;
vector<complex>a(2100005),b(2100005);

int main()
{
    n=read(),m=read();
    for(int i=0;i<=n;i++) a[i].a=read();
    for(int i=0;i<=m;i++) b[i].a=read();
    int limit=1;
    while(limit<=n+m) limit<<=1;
    fft(a,limit,1);fft(b,limit,1);
    for(int i=0;i<=limit;i++) a[i]=a[i]*b[i];
    fft(a,limit,-1);
    for(int i=0;i<=n+m;i++) printf("%d ",(int)(a[i].a/limit+0.5));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值