算法笔记--FFT
推荐阅读资料:算法导论第30章
本文不做证明,详细证明请看如上资料。
FFT在算法竞赛中主要用来加速多项式的乘法
普通是多项式乘法时间复杂度的是O(n2),而用FFT求多项式的乘法可以使时间复杂度达到O(nlogn)
FFT求多项式的乘法步骤主要如下图

其中求值是将系数表达转换成点值表达,带入的自变量是wn=1的复数解,称为DFT
插值是将点值表达转换成系数表达,称为DFT-1
DFT 和 DFT-1都可以用FFT加速实现



这是递归版的FFT
还有一种非递归的版本
我们发现叶子节点的下表的二进制为:000 100 010 110 001 101 110 111
与它们的本身所对应的位置的二进制:000 001 010 011 100 101 011 111
相反
所以我们可以确定叶子节点的值,从下往上进行操作
求二进制反转的代码(其中L是二进制位):
for (int i = ; i < n; i++) {
R[i] = (R[i>>]>>) | ((i&) << L-);
}
假设现在R[i]的二进制是abcd,没有操作之前的R[i>>1]是0abc,操作之后的是cba0,再右移是0cba,再判断原来的d是不是1在最高位放1或0,就刚好是反转的结果
模板:
递归版(以求大数乘法为例):
#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pi acos(-1.0)
#define LL long long
#define mp make_pair
#define pb push_back
#define ls rt<<1, l, m
#define rs rt<<1|1, m+1, r
#define ULL unsigned LL
#define pll pair<LL, LL>
#define pii pair<int, int>
#define piii pair<int,pii>
#define mem(a, b) memset(a, b, sizeof(a))
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fopen freopen("in.txt", "r", stdin);freopen("out.txt", "w", stout);
//head typedef complex<double> cd;
const int N = 2e5 + ;
char a[N], b[N];
cd A[N], B[N];
int tmp[N];
void fft(cd *x, int n, int type) {
if(n == ) return ;
cd l[n>>], r[n>>];
for (int i = ; i < n; i += ) {
l[i>>] = x[i];
r[i>>] = x[i+];
}
fft(l, n>>, type);
fft(r, n>>, type);
cd wn(cos(*pi/n), sin(type**pi/n)), w(, ), t;
for(int i = ; i < n>>; i++, w *= wn) {
t = w*r[i];
x[i] = l[i] + t;
x[i+(n>>)] = l[i] - t;
}
}
int main() {
while(~scanf("%s%s", a, b)) {
int n = strlen(a), m = strlen(b);
mem(A, );
mem(B, );
mem(tmp, );
for (int i = n - ; i >= ; i--) A[n--i] = a[i] - '';
for (int i = m - ; i >= ; i--) B[m--i] = b[i] - '';
m = m + n;
for(n = ; n <= m; n <<= );
fft(A, n, );
fft(B, n, );
for (int i = ; i < n; i++) A[i] *= B[i];
fft(A, n, -);
for (int i = ; i < m; i++) {
int t = (int)(A[i].real()/n + 0.5);
t += tmp[i];
tmp[i] = t%;
tmp[i+] += t/;
}
int i;
for (i = m; i >= ; i--) if(tmp[i]) break;
for (i; i >= ; i--) printf("%d", tmp[i]);
printf("\n");
}
return ;
}
FFT非递归版模板:
typedef complex<double> cd;
const int N = 2e5 + ;
cd A[N], B[N];
int R[N];
void fft(cd *x, int n, int type) {
for (int i = ; i < n; i++) if(i < R[i]) swap(x[i], x[R[i]]);
for (int i = ; i < n; i <<= ) {
cd wn(cos(pi/i), type*sin(pi/i));
for (int j = ; j < n; j += i<<) {
cd w(, );
for (int k = ; k < i; k++, w*=wn) {
cd X = x[j+k], Y = w*x[j+k+i];
x[j+k] = X+Y;
x[j+k+i] = X-Y;
}
}
}
if(type == -) {
for (int i = ; i < n; ++i) x[i]=(x[i].real()/n,x[i].imag());
}
} int main() {
int n, m, L = ;
scanf("%d %d", &n, &m);
for (int i = ; i < n; ++i) scanf("%d", &A[i]);
for (int i = ; i < m; ++i) scanf("%d", &B[i]);
m = m + n;
for(n = ; n <= m; n <<= ) L++;
for (int i = ; i < n; i++) R[i] = (R[i>>]>>) | ((i&) << L-);
fft(A, n, );
fft(B, n, );
for (int i = ; i < n; i++) A[i] *= B[i];
fft(A, n, -);
for (int i = ; i < m; i++) printf("%d\n", (int)(A[i].real()+0.5));
return ;
}
PS:手写complex类+非递归版最快
NTT模板:
#include<bits/stdc++.h>
using namespace std;
/*
469762049--3
998244353--3
1004535809--3
1e9+7 -- 5
(g 是mod(r*2^k+1)的原根)
素数 r k g
3 1 1 2
5 1 2 2
17 1 4 3
97 3 5 5
193 3 6 5
257 1 8 3
7681 15 9 17
12289 3 12 11
40961 5 13 3
65537 1 16 3
786433 3 18 10
5767169 11 19 3
7340033 7 20 3
23068673 11 21 3
104857601 25 22 3
167772161 5 25 3
469762049 7 26 3
1004535809 479 21 3
2013265921 15 27 31
2281701377 17 27 3
3221225473 3 30 5
75161927681 35 31 3
77309411329 9 33 7
*/ const int N = , P = ;
inline int qpow(int x, int y) {
int res();
while (y) {
if (y & ) res = 1ll * res * x % P;
x = 1ll * x * x % P;
y >>= ;
}
return res;
} int r[N];
void ntt(int *x, int n, int opt) {
register int i, j, k, m, gn, g, tmp;
for (i = ; i < n; ++i)
if (r[i] < i) swap(x[i], x[r[i]]);
for (m = ; m <= n; m <<= ) {
k = m >> ;
gn = qpow(, (P - ) / m); ///3是原根
for (i = ; i < n; i += m) {
g = ;
for (j = ; j < k; ++j, g = 1ll * g * gn % P) {
tmp = 1ll * x[i + j + k] * g % P;
x[i + j + k] = (x[i + j] - tmp + P) % P;
x[i + j] = (x[i + j] + tmp) % P;
}
}
}
if (opt == -) {
reverse(x + , x + n);
register int inv = qpow(n, P - );
for (i = ; i < n; ++i) x[i] = 1ll * x[i] * inv % P;
}
} int A[N], B[N], C[N]; int main() {
int n, m, L = ;
scanf("%d %d", &n, &m);
++n, ++m;
for (int i = ; i < n; ++i) scanf("%d", &A[i]);
for (int i = ; i < m; ++i) scanf("%d", &B[i]);
m = m + n;
for(n = ; n <= m; n <<= ) L++;
for (int i = ; i < n; i++) r[i] = (r[i>>]>>) | ((i&) << L-);
ntt(A, n, );
ntt(B, n, );
for (int i = ; i < n; ++i) C[i] = 1ll * A[i] * B[i] % P;
ntt(C, n, -);
for (int i = ; i < m-; ++i) printf("%d ", C[i]);
puts("");
return ;
}
任意模数NTT模板:
const int maxn = ,maxm = ;
int pr[]={,,};
int R[maxn];
inline LL qpow(LL a,LL b,LL p){
LL re = ; a %= p;
for (; b; b >>= ,a = a * a % p)
if (b & ) re = re * a % p;
return re;
}
struct FFT{
int G,P,A[maxn];
void NTT(int* a,int n,int f){
for (int i = ; i < n; i++) if (i < R[i]) swap(a[i],a[R[i]]);
for (int i = ; i < n; i <<= ){
int gn = qpow(G,(P - ) / (i << ),P);
for (int j = ; j < n; j += (i << )){
int g = ,x,y;
for (int k = ; k < i; k++,g = 1ll * g * gn % P){
x = a[j + k],y = 1ll * g * a[j + k + i] % P;
a[j + k] = (x + y) % P,a[j + k + i] = (x + P - y) % P;
}
}
}
if (f == ) return;
int nv = qpow(n,P - ,P); reverse(a + ,a + n);
for (int i = ; i < n; i++) a[i] = 1ll * a[i] * nv % P;
}
}fft[];
int F[maxn],G[maxn],B[maxn],deg1,deg2,deg,md;
LL ans[maxn];
LL inv(LL n,LL p){return qpow(n % p,p - ,p);}
LL mul(LL a,LL b,LL p){
LL re = ;
for (; b; b >>= ,a = (a + a) % p)
if (b & ) re = (re + a) % p;
return re;
}
void CRT(){
deg = deg1 + deg2;
LL a,b,c,t,k,M = 1ll * pr[] * pr[];
LL inv1 = inv(pr[],pr[]),inv0 = inv(pr[],pr[]),inv3 = inv(M % pr[],pr[]);
for (int i = ; i <= deg; i++){
a = fft[].A[i],b = fft[].A[i],c = fft[].A[i];
t = (mul(a * pr[] % M,inv1,M) + mul(b * pr[] % M,inv0,M)) % M;
k = ((c - t % pr[]) % pr[] + pr[]) % pr[] * inv3 % pr[];
ans[i] = ((k % md) * (M % md) % md + t % md) % md;
}
}
void conv(){
int n = ,L = ;
while (n <= (deg1 + deg2)) n <<= ,L++;
for (int i = ; i < n; i++) R[i] = (R[i >> ] >> ) | ((i & ) << (L - ));
for (int u = ; u <= ; u++){
fft[u].G = ; fft[u].P = pr[u];
for (int i = ; i <= deg1; i++) fft[u].A[i] = F[i];
for (int i = ; i <= deg2; i++) B[i] = G[i];
for (int i = deg2 + ; i < n; i++) B[i] = ;
fft[u].NTT(fft[u].A,n,); fft[u].NTT(B,n,);
for (int i = ; i < n; i++) fft[u].A[i] = 1ll * fft[u].A[i] * B[i] % pr[u];
fft[u].NTT(fft[u].A,n,-);
}
}
int main(){
scanf("%d %d %d", °1, °2, &md);
for (int i = ; i <= deg1; i++) scanf("%d", &F[i]);
for (int i = ; i <= deg2; i++) scanf("%d", &G[i]);
conv(); CRT();
for (int i = ; i <= deg; i++) printf("%lld ",ans[i]);
return ;
}
算法笔记--FFT的更多相关文章
- 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)
再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...
- 「算法笔记」快速数论变换(NTT)
一.简介 前置知识:多项式乘法与 FFT. FFT 涉及大量 double 类型数据操作和 \(\sin,\cos\) 运算,会产生误差.快速数论变换(Number Theoretic Transfo ...
- 学习Java 以及对几大基本排序算法(对算法笔记书的研究)的一些学习总结(Java对算法的实现持续更新中)
Java排序一,冒泡排序! 刚刚开始学习Java,但是比较有兴趣研究算法.最近看了一本算法笔记,刚开始只是打算随便看看,但是发现这本书非常不错,尤其是对排序算法,以及哈希函数的一些解释,让我非常的感兴 ...
- 算法笔记--数位dp
算法笔记 这个博客写的不错:http://blog.csdn.net/wust_zzwh/article/details/52100392 数位dp的精髓是不同情况下sta变量的设置. 模板: ]; ...
- 算法笔记--lca倍增算法
算法笔记 模板: vector<int>g[N]; vector<int>edge[N]; ][N]; int deep[N]; int h[N]; void dfs(int ...
- 算法笔记--STL中的各种遍历及查找(待增)
算法笔记 map: map<string,int> m; map<string,int>::iterator it;//auto it it = m.begin(); whil ...
- 算法笔记--priority_queue
算法笔记 priority_queue<int>que;//默认大顶堆 或者写作:priority_queue<int,vector<int>,less<int&g ...
- 算法笔记--sg函数详解及其模板
算法笔记 参考资料:https://wenku.baidu.com/view/25540742a8956bec0975e3a8.html sg函数大神详解:http://blog.csdn.net/l ...
- 算法笔记——C/C++语言基础篇(已完结)
开始系统学习算法,希望自己能够坚持下去,期间会把常用到的算法写进此博客,便于以后复习,同时希望能够给初学者提供一定的帮助,手敲难免存在错误,欢迎评论指正,共同学习.博客也可能会引用别人写的代码,如有引 ...
随机推荐
- centos6安装多实例mysql
centos 6.5,使用二进制安装多实例mysql 5.5.60 所需安装包mysql-5.5.60-linux-glibc2.12-x86_64.tar.gz.ncurses-devel-5.7- ...
- http 请求头大小写的问题
如果是默认消息头名称,消息头格式已经固定,即便输入的大小写有误,也会给你翻译成默认的写法,如果自己定义的,会自动给你翻译成小写,所以传参数的名称都用小写字母即可,否则可能取不到值,比如encrypte ...
- wait()和notify()的理解与使用
void notify() Wakes up a single thread that is waiting on this object’s monitor. 译:唤醒在此对象监视器上等待的单个线程 ...
- 【python003-变量】
变量 一.在使用变量之前,需要先对其进行赋值 二.变量命名的规则:可以包含字母,数字,下划线,但是不能以数字开头 三.字符串: 1.引号内的一切东西 2.python的字符串是要在两边加上引号,对于单 ...
- Lottie的json动画
我们的设计如是说: Lottie的json动画,优缺点有 优点: 1.开发成本低,动画的还原度高.设计师导出 json 文件后,交付开发.原本要1天甚至更久的动画实现,现在只要不到一小时甚至更少时间了 ...
- Bootstrap3基础 caret 辅助类样式 下拉的小三角
内容 参数 OS Windows 10 x64 browser Firefox 65.0.2 framework Bootstrap 3.3.7 editor ...
- vue学习【第三篇】:vue之node.js的简单介绍
什么是node.js 它是可以运行JavaScript的服务平台,可以吧它当做一门后端程序,只是它的开发语言是JavaScript 安装node.js node.js的特性 - 非阻塞IO模型 - 时 ...
- DNS 基础
DNS Domain Name System:域名系统,主要用来将域名解析为IP.DNS以及相关系统存在,主要有两个原因: 可以让人容易记住名字,而不是记住IP: 它允许服务器改变IP,但是采用同样的 ...
- POJ 1191 棋盘分割(区间DP)题解
题意:中文题面 思路:不知道直接暴力枚举所有情况行不行... 我们可以把答案转化为 所以答案就是求xi2的最小值,那么我们可以直接用区间DP来写.设dp[x1][y1][x2][y2][k]为x1 y ...
- 【做题】CFedu41G. Partitions——推式子
实际上这题的题面还是颇有意思,对两个划分不同的定义暗示了第二类斯特林数,模数是\(1000000007\)又表明这题不是NTT. 那么一开始的想法是考虑每个集合的贡献.设这个集合为\(S\),那么它的 ...