多项式乘法优化 学习笔记

今早重新看了myy的论文,又掌握了一些多项式乘法的新姿势,于是写一篇blog巩固一下QAQ。


①如何用一次DFT加一次IDFT求出两个实序列A和B的卷积?

这里我们只要求卷积后的结果,不需要求DFT的值,所以有一种很简便的方法:令复数序列C的实部为A,虚部为B。将其自卷,所得结果虚部的值除以2就是要求的多项式。

这个十分容易证明:

C2[k]=j=0kC[j]C[kj] C 2 [ k ] = ∑ j = 0 k C [ j ] C [ k − j ]

=j=0k(A[j]+iB[j])(A[kj]+iB[kj]) = ∑ j = 0 k ( A [ j ] + i ∗ B [ j ] ) ( A [ k − j ] + i ∗ B [ k − j ] )

=j=0k(A[j]A[kj]B[j]B[kj])+ij=0k(A[j]B[kj]+B[j]A[kj]) = ∑ j = 0 k ( A [ j ] A [ k − j ] − B [ j ] B [ k − j ] ) + i ∗ ∑ j = 0 k ( A [ j ] B [ k − j ] + B [ j ] A [ k − j ] )


②如何用一次DFT同时求出两个实序列在单位复数根处的点值?

这个推导就很复杂了,大概就是一堆三角函数和 i i 换来换去,具体要看myy的论文,我也不再赘述。最终概括一下做法,就是设:

P(x)=A(x)+iB(x)

Q(x)=A(x)iB(x) Q ( x ) = A ( x ) − i ∗ B ( x )

P(x),Q(x) P ( x ) , Q ( x ) 在单位复数根处的点值表达分别为 FP,FQ F P , F Q ,则可以证明 FP(k) F P ( k ) FQ(Nk) F Q ( N − k ) 互为共轭复数。因此只需要对 P(x) P ( x ) 进行DFT即可。然后会有:

DFTA=FP+FQ2 D F T A = F P + F Q 2

DFTB=FPFQ2i D F T B = F P − F Q 2 i

(myy论文里第二条式子写的是乘以 i i ,不过我觉得是除以i才对)


③关于单位复数根 ωkN ω N k

这个有三种算法。一种是累乘,时间快但精度不高。还有一种是直接每次用 2πkN 2 π k N 的三角函数去算,这样比较慢,但精度高。最后一种是预处理,然后用vector之类的存下来。第三种方法比较折中,在拆系数+FFT处理任意模数多项式卷积的时候经常用。


贴一份模板题(洛谷P3803)的CODE:
因为只用了两次DFT,所以完全不怕卡常

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
#include<vector>
using namespace std;

const int maxn=4000000;
const double pi=acos(-1.0);

struct Complex
{
    double X,Y;
    Complex (double a=0.0,double b=0.0) : X(a),Y(b) {}
} ;

Complex operator+(Complex a,Complex b){return Complex(a.X+b.X,a.Y+b.Y);}
Complex operator-(Complex a,Complex b){return Complex(a.X-b.X,a.Y-b.Y);}
Complex operator*(Complex a,Complex b){return Complex(a.X*b.X-a.Y*b.Y,a.X*b.Y+a.Y*b.X);}

Complex A[maxn];
Complex B[maxn];

vector <Complex> w[maxn];
int Rev[maxn];
int N,Lg;

int F[maxn];
int G[maxn];
int n,m;

void DFT(Complex *a,double f)
{
    for (int i=0; i<N; i++)
        if (i<Rev[i]) swap(a[i],a[ Rev[i] ]);

    for (int len=2; len<=N; len<<=1)
    {
        int mid=(len>>1);
        for (Complex *p=a; p!=a+N; p+=len)
            for (int i=0; i<mid; i++)
            {
                Complex temp=w[mid][i];
                if (f==-1.0) temp.Y=-temp.Y;
                temp=temp*p[mid+i];
                p[mid+i]=p[i]-temp;
                p[i]=p[i]+temp;
            }
    }
}

void FFT()
{
    N=1,Lg=0;
    while (N<n+m+4) N<<=1,Lg++;
    for (int i=0; i<N; i++)
        for (int j=0; j<Lg; j++)
            if (i&(1<<j)) Rev[i]|=(1<<(Lg-j-1));

    int len=1;
    while ((len<<1)<=N)
    {
        double ang=pi/len;
        for (int i=0; i<len; i++)
            w[len].push_back( Complex( cos(ang*(double)i) , sin(ang*(double)i) ) );
        len<<=1;
    }

    for (int i=0; i<N; i++) A[i]=Complex((double)F[i],(double)G[i]);
    DFT(A,1.0);
    for (int i=0; i<N; i++) B[i]=A[(N-i)%N],B[i].Y=-B[i].Y;
    for (int i=0; i<N; i++)
    {
        Complex a=A[i]+B[i];
        a.X/=2.0;
        a.Y/=2.0;
        B[i]=A[i]-B[i];
        B[i].X/=2.0;
        B[i].Y/=2.0;
        swap(B[i].X,B[i].Y);
        B[i].Y=-B[i].Y;
        A[i]=a;
    }
    for (int i=0; i<N; i++) A[i]=A[i]*B[i];
    DFT(A,-1.0);
    for (int i=0; i<N; i++) A[i].X/=((double)N);
    for (int i=0; i<N; i++) F[i]=(int)floor( A[i].X+0.5 );
}

int main()
{
    freopen("3803.in","r",stdin);
    freopen("3803.out","w",stdout);

    scanf("%d%d",&n,&m);
    for (int i=0; i<=n; i++) scanf("%d",&F[i]);
    for (int i=0; i<=m; i++) scanf("%d",&G[i]);

    FFT();
    for (int i=0; i<=n+m; i++) printf("%d ",F[i]);
    printf("\n");

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值