洛谷P3803 fft模板

概念:

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

1. 1. 1.系数表示法,唯一确定一个 n n n次的多项式需要每一项的系数,从低次项到高次项依次写成向量的形式,这个向量就能唯一确定一个多项式。即向量 r = [ a 0 , a 1 , . . . . . , a n ] r=[a_{0},a_{1},.....,a_{n}] r=[a0,a1,.....,an],就能唯一确定多项式 f ( x ) = a 0 + a 1 x + a 2 x 2 + . . . + a n x n f(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 ( n 2 ) O(n^{2}) O(n2)

等长度向量相乘的方法:

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

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

单位根的概念:

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

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

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

性质 2 : n 2:n 2n次单位根有 n n n个,并且第 k k k个单位根写作 w n k w_{n}^{k} wnk,有 w n k = r e 2 π k n w_{n}^{k}=re^{2\pi\frac{k}{n}} wnk=re2πnk,单位根是从 0 0 0标号到 n − 1 n-1 n1的。

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

性质 3 3 3 w d n d k = w n k w_{dn}^{dk}=w_{n}^{k} wdndk=wnk w n k + n 2 = w n 2 k w_{n}^{k+\frac{n}{2}}=w_{\frac{n}{2}}^{k} wnk+2n=w2nk

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

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

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

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

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

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

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

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

对应的多项式是:

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

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

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

把两个形式变换得到:

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

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

带入得到:

f ( w n k ) = f [ 0 ] ( ( w n k ) 2 ) + w n k f [ 1 ] ( ( w n k ) 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 ( w n k ) = f [ 0 ] ( w n 2 k ) + w n k f [ 1 ] ( w n 2 k ) 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)

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

f ( w n k + n 2 ) = f [ 0 ] ( w n 2 k ) − w n k f [ 1 ] ( w n 2 k ) 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)得到的。

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

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

a i = c i n a_{i}=\frac{c_{i}}{n} ai=nci

由于 f f t fft fft使用 d o u b l e double double保存复数向量,所以最后要四舍五入,这里也给出一种四舍五入的方式: ( i n t ) ( a + 0.5 ) (int)(a+0.5) (int)(a+0.5)

由于算法基于对称的方法,所以只适合规模是 2 2 2的幂次的情形,因此,如果规模不为 2 2 2的幂次,需要添加额外项使规模是 2 2 2的幂,添加的额外项系数是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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值