1. 程式人生 > >[uoj#34] [洛谷P3803] 多項式乘法(FFT)

[uoj#34] [洛谷P3803] 多項式乘法(FFT)

spl logs con 常見 scanf printf namespace while 神奇

新技能——FFT。
可在 \(O(nlogn)\) 時間內完成多項式在系數表達與點值表達之間的轉換。
其中最關鍵的一點便為單位復數根,有神奇的折半性質。
多項式乘法(即為卷積)的常見形式:
\[ C_n=\sum\limits_{i=0}^n A_iB_{n-i} \]
基本思路為先將系數表達 -> 點值表達 \(O(nlogn)\)
隨後點值 \(O(n)\) 進行乘法運算
最後將點值表達 -> 系數表達 \(O(nlogn)\)


代碼

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

const int N = 2100005;
const double pi = 3.1415926535897932384626433832795;

struct c{
    double r,i;
    c() { r=i=0.0; }
    c(double x,double y) { r=x; i=y; }
    c operator + (const c &b) { return c(r+b.r,i+b.i); }
    c operator += (const c &b) { return *this=*this+b; }
    c operator - (const c &b) { return c(r-b.r,i-b.i); }
    c operator -= (const c &b) { return *this=*this-b; }
    c operator * (const c &b) { return c(r*b.r-i*b.i,r*b.i+b.r*i); }
    c operator *= (const c &b) { return *this=*this*b; }
}a[N],b[N],x[N];

int l;
int r[N];
void fft(c A[],int ty){
    for(int i=0;i<l;i++) x[r[i]]=A[i];
    for(int i=0;i<l;i++) A[i]=x[i];
    for(int i=2;i<=l;i<<=1){
        c wn(cos(pi*2/i),ty*sin(pi*2/i));
        for(int j=0;j<l;j+=i){
            c w(1,0);
            for(int k=j;k<j+i/2;k++){
                c t=A[k+i/2]*w;
                A[k+i/2]=A[k]-t;
                A[k]+=t;
                w*=wn;
            }
        }
    }
}

int n,m;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<=n;i++) scanf("%lf",&a[i].r);
    for(int i=0;i<=m;i++) scanf("%lf",&b[i].r);
    
    l=1;
    while(l<=n+m) l<<=1;
    for(int i=0;i<l;i++) r[i]=(r[i>>1]>>1)|((i&1)*(l>>1));
    
    fft(a,1);fft(b,1);
    for(int i=0;i<l;i++)
        a[i]*=b[i];
    fft(a,-1);
    
    for(int i=0;i<n+m;i++)
        printf("%d ",int(a[i].r/l+0.5));
    printf("%d",int(a[n+m].r/l+0.5));
   
    return 0;    
}

[uoj#34] [洛谷P3803] 多項式乘法(FFT)