FFT\NTT总结

学了好久,终于基本弄明白了

推荐两个博客:
戳我
戳我
再推荐几本书:
《ACM/ICPC算法基础训练教程》
《组合数学》(清华大学出版社)
《高中数学选修》

预备知识

复数方面

找数学老师去

i2=1i i 2 = − 1 , i 为 虚 数 的 单 位

坐标系上纵轴就是虚数轴,复数就是这上面的点
三种表示法:
a+biab 一 般 : a + b i , a 为 实 部 , b 为 虚 部

eiθ 指 数 : e i θ ∗ 坐 标 系 上 的 模 长

(cosθ+isinθ) 三 角 : 模 长 ∗ ( c o s θ + i s i n θ )

运算:
加减法:实部虚部分别相加
乘法:
(a+bi)(c+di)=ac+adi+bci+bdi2=acbd+(ad+bc)i ( a + b i ) ∗ ( c + d i ) = a c + a d i + b c i + b d i 2 = a c − b d + ( a d + b c ) i

欧拉公式

eix=cosx+isinx() e i x = c o s x + i s i n x ( 就 是 指 数 表 示 和 三 角 表 示 )

eiπ=1 特 别 的 e i π = − 1

多项式

A(x)=Σn1k=0akxk 系 数 表 示 法 : A ( x ) = Σ k = 0 n − 1 a k x k

xkA(x)yk 点 值 表 示 法 : 对 于 所 有 的 x k , 求 出 它 们 对 应 的 A ( x ) , 设 为 y k

{(x0,y0),(x1,y1),......,(xn1,yn1)} 则 可 以 用 { ( x 0 , y 0 ) , ( x 1 , y 1 ) , . . . . . . , ( x n − 1 , y n − 1 ) } 表 示 这 个 多 项 式 并 且 是 唯 一 确 定 的

单位复数根

nωn=1nne2kπink=0n1 n 次 单 位 复 数 根 ω n = 1 , n 次 单 位 复 数 根 刚 好 有 n 个 对 应 e 2 k π i n , 其 中 k = 0 到 n − 1

三个性质:
消去引理:
n,d,kωdkdn=ωkn n , d , k 为 正 整 数 , 则 ω d n d k = ω n k

e2kπin 证 明 : 套 e 2 k π i n 即 可

折半引理:
n(ωk+n2n)2=ω2k+nn=ω2knωnn=(ωkn)2 n 为 大 于 零 的 偶 数 , 则 ( ω n k + n 2 ) 2 = ω n 2 k + n = ω n 2 k ω n n = ( ω n k ) 2

求和引理:
大于1的整数n,和不被n整除的非负整数k,有
Σn1j=0(ωkn)j=0 Σ j = 0 n − 1 ( ω n k ) j = 0

证明可以用等比数列求和公式得到(很简单的,手推一遍就好)

Rader排序

其实就是二进制数位翻转

正题

DFT

对于k=0~n-1,定义:

yk=A(ωkn)=Σn1j=0aj(ωkn)j y k = A ( ω n k ) = Σ j = 0 n − 1 a j ( ω n k ) j

yay=DFTn(a)(yayk,aky,a) 得 到 的 y 称 为 a 的 离 散 傅 里 叶 变 换 , 记 作 y = D F T n ( a ) ( 这 里 的 y , a 指 的 是 所 有 的 y k , a k , 即 向 量 y , a )

逆DFT

DFTaDFT1 就 是 D F T 的 逆 变 换 , 求 出 向 量 a , 记 为 D F T − 1

假设得到了向量y
yk=Σn1i=0ai(ωk)i 对 于 y k = Σ i = 0 n − 1 a i ( ω k ) i

ak=1nΣn1i=0yi(ωk)i 有 a k = 1 n Σ i = 0 n − 1 y i ( ω − k ) i

ak=1nΣn1i=0yi(ωk)i=1nΣn1i=0(Σn1j=0aj(ωk)j)(ωk)i=1nΣn1i=0ai(Σn1j=0(ωjk)i) 证 明 : a k = 1 n Σ i = 0 n − 1 y i ( ω − k ) i = 1 n Σ i = 0 n − 1 ( Σ j = 0 n − 1 a j ( ω k ) j ) ( ω − k ) i = 1 n Σ i = 0 n − 1 a i ( Σ j = 0 n − 1 ( ω j − k ) i )

akjk0j=k1 可 以 用 等 比 数 列 求 和 出 上 面 的 就 是 a k ( 当 j ≠ k 是 括 号 里 的 为 0 , 当 j = k 时 为 1 )

FFT

上面已经把DFT和逆DFT搞定了,两个几乎是一样的

所以求多项式的积(卷积)可以用DFT转换成点值表示,就可以O(n),一一相乘,得到积的多项式的点值表示,最后用逆DFT得到系数表示

复杂度瓶颈在于怎样快速求解DFT(逆DFT和DFT方法一样)

FFT就是一个O(nlogn)求解DFT的方法

首先把A(x)分成奇数项和偶数项记作

A[0](x)=a0+a2x+a4x2+...+an2xn21 A [ 0 ] ( x ) = a 0 + a 2 x + a 4 x 2 + . . . + a n − 2 x n 2 − 1

A[1](x)=a1+a3x+a5x2+...+an1xn21 A [ 1 ] ( x ) = a 1 + a 3 x + a 5 x 2 + . . . + a n − 1 x n 2 − 1

A(x)=A[0](x2)+xA[1](x2) 显 然 A ( x ) = A [ 0 ] ( x 2 ) + x A [ 1 ] ( x 2 )

那么
A(ωkn)=A[0]((ωkn)2)+ωknA[1]((ωkn)2)=A[0](ωkn2)+ωknA[1](ωkn2) A ( ω n k ) = A [ 0 ] ( ( ω n k ) 2 ) + ω n k A [ 1 ] ( ( ω n k ) 2 ) = A [ 0 ] ( ω n 2 k ) + ω n k A [ 1 ] ( ω n 2 k )

ωn2n=ω2=ekπi=coskπ+isinkπ=1 因 为 ω n n 2 = ω 2 = e k π i = c o s k π + i s i n k π = − 1

A(ωk+n2n)=A[0](ωkn2)ωknA[1](ωkn2) 所 以 A ( ω n k + n 2 ) = A [ 0 ] ( ω n 2 k ) − ω n k A [ 1 ] ( ω n 2 k )

这称为蝴蝶操作

于是对每个y值的求解可以通过分组求出,若递归变成处理子任务,这样复杂度就成了O(nlogn)

这样不停地分组,最后就相当于Rader排序了一番,所以也可以变成非递归的

注意每次都要把多项式补成2的幂,便于FFT

递归写可能好理解一些,但不好写

还有一些东西什么的,其实记一记就好了其实自己说不清

系统的复数complex代码

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int _(3e6 + 10);
const double Pi = acos(-1);

IL ll Read(){
    char c = '%'; ll x = 0, z = 1;
    for(; c > '9' || c < '0'; c = getchar()) if(c == '-') z = -1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
    return x * z;
}

int n, m, r[_], l;
complex <double> a[_], b[_];

IL void FFT(complex <double> *P, int opt){
    for(RG int i = 0; i < n; ++i) if(i < r[i]) swap(P[i], P[r[i]]); //Rader排序
    for(RG int i = 1; i < n; i <<= 1){
        complex <double> W(cos(Pi / i), opt * sin(Pi / i));  //旋转因子
        for(RG int p = i << 1, j = 0; j < n; j += p){
            complex <double> w(1, 0);
            for(RG int k = 0; k < i; ++k, w *= W){
                complex <double> X = P[j + k], Y = w * P[j + k + i];
                P[j + k] = X + Y; P[j + k + i] = X - Y;    //蝴蝶操作
            }
        }
    }
}

int main(RG int argc, RG char *argv[]){
    n = Read(); m = Read();
    for(RG int i = 0; i <= n; ++i) a[i] = Read();
    for(RG int i = 0; i <= m; ++i) b[i] = Read();
    m += n;
    for(n = 1; n <= m; n <<= 1) ++l;//补成2的幂
    for(RG int i = 0; i < n; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));//Rader排序预处理
    FFT(a, 1); FFT(b, 1); //DFT
    for(RG int i = 0; i < n; ++i) a[i] = a[i] * b[i]; //点值直接相乘
    FFT(a, -1);  //逆DFT
    for(RG int i = 0; i <= m; ++i) printf("%d ", (int)(a[i].real() / n + 0.5));
    return 0;
}

或者可以自己定义complex,用复数运算

struct Complex{
    double real, image;
    IL Complex(){  real = image = 0;  }
    IL Complex(RG double a, RG double b){  real = a; image = b;  }
    IL Complex operator +(RG Complex B){  return Complex(real + B.real, image + B.image);  }
    IL Complex operator -(RG Complex B){  return Complex(real - B.real, image - B.image);  }
    IL Complex operator *(RG Complex B){  return Complex(real * B.real - image * B.image, real * B.image + image * B.real);  }
}

NTT(快速数论变换)

前置技能原根
g g p(质数)的原根
e2πinωngp1n(mod p) e 2 π i n ≡ ω n ≡ g p − 1 n ( m o d   p )
带进去就好了
Reverse的那个不会证明
UOJ U O J 的模板

# include <bits/stdc++.h>
# define RG register
# define IL inline
# define Fill(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long ll;
const int Zsy(998244353);
const int Phi(998244352);
const int G(3);
const int _(4e5 + 5);

IL ll Input(){
    RG ll x = 0, z = 1; RG char c = getchar();
    for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
    return x * z;
}

int n, m, N, l, r[_], A[_], B[_];

IL int Pow(RG ll x, RG ll y){
    RG ll ret = 1;
    for(; y; y >>= 1, x = x * x % Zsy)
        if(y & 1) ret = ret * x % Zsy;
    return ret;
}

IL void NTT(RG int *P, RG int opt){
    for(RG int i = 0; i < N; ++i) if(r[i] < i) swap(P[r[i]], P[i]);
    for(RG int i = 1; i < N; i <<= 1){
        RG int W = Pow(G, Phi / (i << 1));
        if(opt == -1) W = Pow(W, Zsy - 2);
        for(RG int j = 0, p = i << 1; j < N; j += p){
            RG int w = 1;
            for(RG int k = 0; k < i; ++k, w = 1LL * w * W % Zsy){
                RG int X = P[k + j], Y = 1LL * w * P[k + j + i] % Zsy;
                P[k + j] = (X + Y) % Zsy, P[k + j + i] = (X - Y + Zsy) % Zsy;
            }
        }
    }
}

int main(RG int argc, RG char* argv[]){
    n = Input(), m = Input();
    for(RG int i = 0; i <= n; ++i) A[i] = Input();
    for(RG int i = 0; i <= m; ++i) B[i] = Input();
    for(n += m, N = 1; N <= n; N <<= 1) ++l;
    for(RG int i = 0; i < N; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    NTT(A, 1); NTT(B, 1);
    for(RG int i = 0; i < N; ++i) A[i] = 1LL * A[i] * B[i] % Zsy;
    NTT(A, -1);
    RG int inv = Pow(N, Zsy - 2);
    for(RG int i = 0; i <= n; ++i) printf("%lld ", 1LL * A[i] * inv % Zsy);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值