(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 最菜!
下面的 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 轴正半轴形成的角)。
在模 p p (为质数)意义下,如果存在数 g g ,满足
步入正题。一个 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 ,那么
多项式的点值表示:选取 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+2x−x2+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,...,ωn−1n ω n 0 , ω n 1 , . . . , ω n n − 1 代入一个 n n 项(注意,不是 n n 次)式 A A ,得到的点值表示,称为 A A 的离散傅氏变换( DFT DFT )。
FFT 采用的是分治的思想。(这里首先要将 n n 变成 2 2 的整数次幂)先将 A A 按照系数奇偶性分类:
已知 A A 的 DFT DFT :
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 每次不是把区间直接分割成两个子区间进行分治,是按照奇偶性进行分治。于是考虑如果 n=2k n = 2 k ,那么设 rev[i] r e v [ i ] 表示 i i 在 k k 位二进制数下的各位数倒转,如:
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;
}
}
}
}
FFT 涉及到复数运算,难免有精度问题。考虑如果题目要求在模意义下怎么做。
思考原根是否有单位根的性质:
(1)根据费马小定理, (gi)p−1≡1(modp) ( g i ) p − 1 ≡ 1 ( mod p ) 。
(2) gp−12≡−1(modp) g p − 1 2 ≡ − 1 ( mod p )
答案是肯定的。
因此当 p=a×2k+1 p = a × 2 k + 1 , n=2x n = 2 x 且 x≤k x ≤ k 时,只要将 ga×2k−x 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 的模数不能分解成 a×2k+1 a × 2 k + 1 的形式,上面的做法就不可用。
解决办法是选取几个质数,使他们的积大于 n(p−1)2 n ( p − 1 ) 2 。
在这几个质数的模意义下做 NTT ,然后用中国剩余定理 CRT 合并后对 p p 取模。
两个定义域在自然数上的函数 f(n),g(n) f ( n ) , g ( n ) ,他们的卷积为:
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