[学习笔记][省选算法]多项式乘法之 FFT & NTT 及卷积

一、开头

(Place :山东省神犇协会第 998244353 会议厅)
SD 神犇 998244353 号(会长):所有神犇,洛谷黑题都切完了吗?
全体神犇:切完了!
神犇 1004535809 号: 998244353 ,我昨天看到一个弱鸡,洛谷名叫 xyz32768 ,他一道黑题都没做,您认为应该怎样呢?
神犇 998244353 号:这个问题您们考虑一下,应该用怎样的方式把 xyz32768 D 一遍。时刻记住,我们协会的口号:见一弱鸡, D 一弱鸡!
神犇 167772161 号:要不,考他 FFT 吧。这是我们协会里每个人都会的算法!
神犇 998244353 号:Good idea !
(Place : FFT 星球 DFT 国 IDFT 省 NTT 市)
神犇 167772161 号:哈哈,好久没见,我们搞 OI 多年,能回忆起的比赛,至少也从三年前的 SDOI 2015 开始吧,那次比赛有一道十分模板的题。题目名称:序列统计。题面你自己去 BZOJ 或洛谷上去查,如果你 1h 内写不出来我会对整个 FFT 星球说你菜!
xyz32768 :BZOJ 3992 / 洛谷 P3321 ?
神犇 1004535809 号:对。这是一道紫题,但此大佬已经将之视为宇宙之水题了。
(蒟蒻 xyz32768 当然不会,看了看算法标签)
xyz32768 : 什么?????? 快速傅里叶变换,DFT,FFT ??????
神犇 998244353 号:哈哈哈哈哈,你真是菜啊, FFT 这么水的算法都不会,再见蒟蒻!
神犇 998244353 号 & 神犇 1004535809 号 & 神犇 167772161 号(大声喊): xyz32768 菜, xyz32768 好菜, xyz32768 最菜!

二、预备知识

1、复数

下面的 i i ,除非作为 求和的变量,其余都表示虚数单位 1 − 1
一个复数可以表示成 a+bi a + b i 的形式,也可以表示成 r(cosθ+isinθ) r ( cos ⁡ θ + i sin ⁡ θ ) 的形式。
上面 a a 为实部, b b 为虚部, r=a2+b2 r = a 2 + b 2 为模长, θ=arctanba θ = arctan ⁡ b a 为辐角。
C++ 中,

#include 
using namespace std;

复数类为( T 为实部和虚部的数据类型):

complex

定义一个复数( a a 为实部, b b 为虚部)

complex  x(a, b)

实部

x.real()

一个复数 a+bi a + b i 对应复平面上一个点 (a,b) ( a , b ) ,模长 r r 为点到原点的距离,辐角 θ θ 为终边为 (0,0)(a,b) ( 0 , 0 ) − ( a , b ) 的角(即该边与 x 轴正半轴形成的角)。

2、单位根

ωn=cos2πn+isin2πn ω n = cos ⁡ 2 π n + i sin ⁡ 2 π n

那么
ω0n,ω1n,ω2n,...,ωn1n ω n 0 , ω n 1 , ω n 2 , . . . , ω n n − 1
称为 n n 次单位根。
如:
ω04=1,ω14=i,ω24=1,ω34=i ω 4 0 = 1 , ω 4 1 = i , ω 4 2 = − 1 , ω 4 3 = − i

n n 次单位根的性质 :
(1) n n 次单位根所表示的点分布在复平面的单位圆上,并且把圆周等分成 n n 段。
(2) n n 次单位根的 n n 次方等于 1 1
(3)
ω2i2n=ωin,ωn2n=1,ωn+i2n=ωi2n ω 2 n 2 i = ω n i , ω 2 n n = − 1 , ω 2 n n + i = − ω 2 n i

3、原根

在模 p p (为质数)意义下,如果存在数 g g ,满足

g0,g1,g2,...,gp2 g 0 , g 1 , g 2 , . . . , g p − 2

互不相同,那么 g g 为模 p p 的原根。
原根都比较小,可以暴力枚举。将 p1 p − 1 分解质因数,对于一个 g g ,如果对于 p1 p − 1 的任何一个质因子 x x ,都有
gp1x1 g p − 1 x ≠ 1

那么 g g 是模 p p 的一个原根。

三、多项式乘法

步入正题。一个 n n 次多项式 A A 和一个 n n 次多项式 B B 相乘,设 A A 的系数为 a0...n a 0... n xi x i 的项系数为 ai a i ), B B 的系数为 b0...n b 0... n ,那么

abk=i+j=kaibj a b k = ∑ i + j = k a i b j

复杂度显然是 O(n2) O ( n 2 ) 的。快速傅里叶变换( FFT )则能做到 O(nlogn) O ( n log ⁡ n ) 的复杂度。

四、多项式的点值表示及 DFT

多项式的点值表示:选取 n+1 n + 1 互不相同的值 x0...n x 0... n 代入 A A 对多项式进行求值,得到 n+1 n + 1 个多项式值,那么这 n+1 n + 1 个自变量和这 n+1 n + 1 个多项式值就是多项式的点值表示。
举例:一个四项式 A=3+2xx2+x3 A = 3 + 2 x − x 2 + x 3
选取自变量 x0=0,x1=1,x2=1,x3=2 x 0 = 0 , x 1 = − 1 , x 2 = 1 , x 3 = 2
求得 A(0)=3,A(1)=1,A(1)=5,A(2)=11 A ( 0 ) = 3 , A ( − 1 ) = − 1 , A ( 1 ) = 5 , A ( 2 ) = 11
这就是 A A 的一种点值表示。
一个多项式的点值表示有无穷多种,但是一个包含 n+1 n + 1 个自变量和多项式值的点值表示能唯一确定一个 n n 次多项式。
回到多项式乘法的内容,考虑到如果把 A,B A , B 变成点值表示,那么就可以在 O(n) O ( n ) 时间里得到 AB A B 的点值表示,然后再把 AB A B 转化成系数表示。
于是,这个算法的瓶颈在于系数与点值表示下的转换。
能否给这 n+1 n + 1 个变量取合适的值,使这个转换能够得到优化呢?
答案是肯定的。自变量取值就是 n+1 n + 1 次单位根。
ω0n,ω1n,...,ωn1n ω n 0 , ω n 1 , . . . , ω n n − 1 代入一个 n n 项(注意,不是 n n 次)式 A A ,得到的点值表示,称为 A A 的离散傅氏变换( DFT DFT )。

五、求 DFT

FFT 采用的是分治的思想。(这里首先要将 n n 变成 2 2 的整数次幂)先将 A A 按照系数奇偶性分类:

A=(a0x0+a2x2+...+an2xn2)+(a1x1+a3x3+...+an1xn1) A = ( a 0 x 0 + a 2 x 2 + . . . + a n − 2 x n − 2 ) + ( a 1 x 1 + a 3 x 3 + . . . + a n − 1 x n − 1 )

=(a0x0+a2x2+...+an2xn2)+x(a1x0+a3x2+...+an1xn2) = ( a 0 x 0 + a 2 x 2 + . . . + a n − 2 x n − 2 ) + x ( a 1 x 0 + a 3 x 2 + . . . + a n − 1 x n − 2 )

设:
B=a0x0+a2x1+...+an2xn21 B = a 0 x 0 + a 2 x 1 + . . . + a n − 2 x n 2 − 1

C=a1x0+a3x1+...+an1xn21 C = a 1 x 0 + a 3 x 1 + . . . + a n − 1 x n 2 − 1

假设已经求得了 B B C C DFT DFT ,那么有:
A(ωin)=B((ωin)2)+ωinC((ωin)2) A ( ω n i ) = B ( ( ω n i ) 2 ) + ω n i C ( ( ω n i ) 2 )

=B(ω2in)+ωinC(ω2in) = B ( ω n 2 i ) + ω n i C ( ω n 2 i )

=B(ωin2)+ωinC(ωin2) = B ( ω n 2 i ) + ω n i C ( ω n 2 i )

因此,对于任何一个 i[0,n2) i ∈ [ 0 , n 2 )
A(ωin)=B(ωin2)+ωinC(ωin2) A ( ω n i ) = B ( ω n 2 i ) + ω n i C ( ω n 2 i )

而由于 ωi+n2n2=ωin2 ω n 2 i + n 2 = ω n 2 i ωi+n2n=ωin ω n i + n 2 = − ω n i ,因此
A(ωi+n2n)=B(ωin2)ωinC(ωin2) A ( ω n i + n 2 ) = B ( ω n 2 i ) − ω n i C ( ω n 2 i )

于是,我们实现了在 O(n) O ( n ) 的时间内,从 DFT(B) DFT ( B ) DFT(C) DFT ( C ) 得到 DFT(A) DFT ( A )
复杂度:设求一个 n n 项式的 DFT DFT 的复杂度为 T(n) T ( n ) ,那么
T(n)=O(n)+2T(n2) T ( n ) = O ( n ) + 2 T ( n 2 )

根据主定理得到
T(n)=nlogn T ( n ) = n log ⁡ n

六、求傅里叶逆变换 IDFT ( DFT1 DFT − 1

已知 A A DFT DFT

A(ω0n),A(ω1n),...,A(ωn1n) A ( ω n 0 ) , A ( ω n 1 ) , . . . , A ( ω n n − 1 )

A A 的系数表示。
解关于 a0,a1,...,an1 a 0 , a 1 , . . . , a n − 1 的线性方程组
a0(ω0n)0+a1(ω0n)1+...+an1(ω0n)n1=A(ω0n)a0(ω1n)0+a1(ω1n)1+...+an1(ω1n)n1=A(ω1n)...a0(ωn1n)0+a1(ωn1n)1+...+an1(ωn1n)n1=A(ωn1n) { a 0 ( ω n 0 ) 0 + a 1 ( ω n 0 ) 1 + . . . + a n − 1 ( ω n 0 ) n − 1 = A ( ω n 0 ) a 0 ( ω n 1 ) 0 + a 1 ( ω n 1 ) 1 + . . . + a n − 1 ( ω n 1 ) n − 1 = A ( ω n 1 ) . . . a 0 ( ω n n − 1 ) 0 + a 1 ( ω n n − 1 ) 1 + . . . + a n − 1 ( ω n n − 1 ) n − 1 = A ( ω n n − 1 )

把方程组写成矩阵形式:
(ω0n)0(ω1n)0...(ωn1n)0(ω0n)1(ω1n)1...(ωn1n)1............(ω0n)n1(ω1n)n1...(ωn1n)n1×a0a1...an1=A(ω0n)A(ω1n)...A(ωn1n) [ ( ω n 0 ) 0 ( ω n 0 ) 1 . . . ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 . . . ( ω n 1 ) n − 1 . . . . . . . . . . . . ( ω n n − 1 ) 0 ( ω n n − 1 ) 1 . . . ( ω n n − 1 ) n − 1 ] × [ a 0 a 1 . . . a n − 1 ] = [ A ( ω n 0 ) A ( ω n 1 ) . . . A ( ω n n − 1 ) ]

设上面三个矩阵分别为 X,Y,Z X , Y , Z ,即
X×Y=Z X × Y = Z

考虑求 X1 X − 1
设矩阵
R=(ω0n)0(ω1n)0...(ω(n1)n)0(ω0n)1(ω1n)1...(ω(n1)n)1............(ω0n)n1(ω1n)n1...(ω(n1)n)n1 R = [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 . . . ( ω n − 0 ) n − 1 ( ω n − 1 ) 0 ( ω n − 1 ) 1 . . . ( ω n − 1 ) n − 1 . . . . . . . . . . . . ( ω n − ( n − 1 ) ) 0 ( ω n − ( n − 1 ) ) 1 . . . ( ω n − ( n − 1 ) ) n − 1 ]

R R i i 行第 j j 列(从 0 0 开始计数)的值为 ωijn ω n − i j
那么 X×R X × R i i 行第 i i 列的值为
j=0n1ωijnωijn ∑ j = 0 n − 1 ω n i j ω n − i j

=j=0n11=n = ∑ j = 0 n − 1 1 = n

i i 行第 j j 列( ij i ≠ j )的值为:
k=0n1ωiknωkjn=k=0n1ω(ij)kn=k=0n1(ωij)k ∑ k = 0 n − 1 ω n i k ω n − k j = ∑ k = 0 n − 1 ω n ( i − j ) k = ∑ k = 0 n − 1 ( ω i − j ) k

=(ωijn)n1ωijn1=0 = ( ω n i − j ) n − 1 ω n i − j − 1 = 0

因此 X1=1nR X − 1 = 1 n R
所以
Y=1nR×Z Y = 1 n R × Z

A A DFT1 DFT − 1 ,只需要用 ωin ω n − i 代替 ωin ω n i 做一遍 DFT DFT ,然后把求得的结果除以 n n 就能得到系数。
注:
ω1n=ωn1n=cos2πnisin2πn ω n − 1 = ω n n − 1 = cos ⁡ 2 π n − i sin ⁡ 2 π n

七、FFT 的递归实现

cyx 为复数类, n 为项数, d 为单位根(如果是逆变换则为单位根的倒数),下为求 a0=y[le],a1=y[le+st],a2=[le+2st],... a 0 = y [ l e ] , a 1 = y [ l e + s t ] , a 2 = [ l e + 2 s t ] , . . . 的 DFT 。

void FFT(int n, cyx *y, int le, int st, cyx *d) {
    if (n == 1) return; int m = n >> 1;
    FFT(m, y, le, st << 1, d);
    FFT(m, y, le + st, st << 1, d); int i;
    for (i = 0; i < m; i++) {
        int pos = 2 * st * i;
        tmp[i] = y[le + pos] + d[i * st] * y[le + pos + st];
        tmp[i + m] = y[le + pos] - d[i * st] * y[le + pos + st];
    }
    for (i = 0; i < n; i++)
        y[le + i * st] = tmp[i];
}

八、FFT 的迭代实现

递归实现的 FFT 会遇到效率问题。我们发现, FFT 每次不是把区间直接分割成两个子区间进行分治,是按照奇偶性进行分治。于是考虑如果 n=2k n = 2 k ,那么设 rev[i] r e v [ i ] 表示 i i k k 位二进制数下的各位数倒转,如:

rev[(11001)2]=(10011)2 r e v [ ( 11001 ) 2 ] = ( 10011 ) 2

递推公式(可以自行理解):
rev[0]=0 r e v [ 0 ] = 0

rev[i]=(rev[i>>1]>>1) or ((i and 1)<<(k1)) r e v [ i ] = ( r e v [ i >> 1 ] >> 1 )  or  ( ( i  and  1 ) << ( k − 1 ) )

如果在这个过程中,对 m=n2w m = n 2 w 个数求 DFT ,那么参加求 DFT 的数的 下标有什么特点?
————二进制表示的后 w w 位相等!
如果令 brev[i]=ai b r e v [ i ] = a i ,那么这个特点就变成了二进制表示的前 w w 位相等,而二进制前 w w 位相等的数构成了一个连续区间!
这样,从小区间合并到大区间,就是 FFT 的迭代实现了。
下面,如果 op = 1 则为 DFT ,否则 op = -1 ,为 IDFT 。

void FFT(int n, cyx *y, int op) {
    int i, j, k;
    for (i = 0; i < n; i++) if (i < rev[i]) swap(y[i], y[rev[i]]);
    for (k = 1; k < n; k <<= 1) {
        cyx x(cos(pi / k), op * sin(pi / k));
        for (i = 0; i < n; i += (k << 1)) {
            cyx w(1, 0);
            for (j = 0; j < k; j++) {
                cyx u = y[i + j], v = w * y[i + j + k];
                y[i + j] = u + v; y[i + j + k] = u - v;
                w = w * x;
            }
        }
    }
}

九、快速数论变换 NTT

FFT 涉及到复数运算,难免有精度问题。考虑如果题目要求在模意义下怎么做。
思考原根是否有单位根的性质:
(1)根据费马小定理, (gi)p11(modp) ( g i ) p − 1 ≡ 1 ( mod p )
(2) gp121(modp) g p − 1 2 ≡ − 1 ( mod p )
答案是肯定的。
因此当 p=a×2k+1 p = a × 2 k + 1 n=2x n = 2 x xk x ≤ k 时,只要将 ga×2kx g a × 2 k − x 替换掉单位根,就能实现 NTT 。
下面是 ZZQ=p=1004535809 Z Z Q = p = 1004535809 时:(注: 3×334845270=1004535810 3 × 334845270 = 1004535810

inline void NTT(const int &n, int *a, const int &op) {
    int i, j, k, r = op == 1 ? 3 : 334845270;
    for (i = 0; i < n; i++) if (i < rev[i]) swap(a[i], a[rev[i]]);
    for (k = 1; k < n; k <<= 1) {
        int x = qpow(r, (ZZQ - 1) / (k << 1), ZZQ);
        for (i = 0; i < n; i += (k << 1)) {
            int w = 1;
            for (j = 0; j < k; j++) {
                int u = a[i + j], v = 1ll * w * a[i + j + k] % ZZQ;
                a[i + j] = (u + v) % ZZQ; a[i + j + k] = (u - v + ZZQ) % ZZQ;
                w = 1ll * w * x % ZZQ;
            }
        }
    }
}

十、NTT 的任意模数问题

如果 NTT 的模数不能分解成 a×2k+1 a × 2 k + 1 的形式,上面的做法就不可用。
解决办法是选取几个质数,使他们的积大于 n(p1)2 n ( p − 1 ) 2
在这几个质数的模意义下做 NTT ,然后用中国剩余定理 CRT 合并后对 p p 取模。

十一、快速卷积

两个定义域在自然数上的函数 f(n),g(n) f ( n ) , g ( n ) ,他们的卷积为:

(fg)(n)=i=0nf(i)g(ni) ( f ⨂ g ) ( n ) = ∑ i = 0 n f ( i ) g ( n − i )

可以发现这是 f(n) f ( n ) g(n) g ( n ) 对应两个多项式( xn x n 的系数分别为 f(n) f ( n ) g(n) g ( n ) )相乘之后的 n n 次项。用 FFT 可以求得 fg f ⨂ g 的所有项值。
如果是求
i=0xf(i)g(i+k) ∑ i = 0 x f ( i ) g ( i + k )

那么将 g g 翻转,令 g(i)=g(ni) g ′ ( i ) = g ( n − i ) ,那么就是 fg f ⨂ g ′ ,仍然是一个卷积的形式。

十二、题目

1、[BZOJ3527][Zjoi2014]力:
https://www.lydsy.com/JudgeOnline/problem.php?id=3527
2、[BZOJ3992][SDOI2015]序列统计:
https://www.lydsy.com/JudgeOnline/problem.php?id=3992
3、[BZOJ4827][Hnoi2017]礼物:
https://www.lydsy.com/JudgeOnline/problem.php?id=4827
4、[BZJ3160]万径人踪灭:
https://www.lydsy.com/JudgeOnline/problem.php?id=3160
5、[BZOJ4555][Tjoi2016&Heoi2016]求和:
https://www.lydsy.com/JudgeOnline/problem.php?id=4555
6、[BZOJ3456]城市规划:
https://www.lydsy.com/JudgeOnline/problem.php?id=3456

你可能感兴趣的:(学习笔记,FFT,NTT)