快速傅里叶变换 & 快速数论变换


[update 3.29.2017]

前言

2月10日初学,记得那时好像是正月十五放假那一天

当时写了手写版的笔记

过去近50天差不多忘光了,于是复习一下,具体请看手写版笔记

参考文献:picks miskcoo menci 阮一峰


Fast Fourier Transform

单位复数根

虚数 复数

\(i\),表示逆时针旋转90度

\(a+bi\),对应复平面上的向量

复数加法 同向量

复数乘法 “模长相乘,幅角相加”,\((a+bi)*(c+di)=ac-bd+adi+bci\)

共轭复数 实部相等,虚部互为相反数. 单位根的倒数等于共轭复数

欧拉公式 \(e^{iu}=cos(u)+isin(u)\)

单位复数根

n次单位复数根:满足\(\omega^n=1\)的复数\(\omega, \omega_n^k = e^{\frac{2\pi i}{n}k}\)

主n次单位根 \(\omega_n = e^{\frac{2\pi i}{n}}\)

消去引理,折半引理,求和引理

\(n\)个\(n\)次单位复数根在乘法意义下形成一个群,与\((Z_n,+)\)有相同的结构,因为\(w(n,0)=w(n,n)=1\ \rightarrow\ w(n,j)*w(n,k)=w(n,(j+k) mod n)\)


FFT

离散傅里叶变换DFT

对于多项式\(A(x)=\sum\limits_{j=0}^{n-1}a_jx^j\),代入n次单位复数根所得到的列向量就是a的离散傅里叶变换

快速傅里叶变换FFT

\(O(nlogn)\)计算离散傅里叶变换

使用分治的思想,按下标奇偶分类,\(A_0(x)\)是偶数项,\(A_1(x)\)是奇数项,则\(A(x)=A_0(x^2)+xA_1(x^2)\),根据折半引理仅有\(\frac{n}{2}\)次单位复数根组成

\(k < \frac{n}{2},\)

\[A(\omega_n^k)=A_0(\omega_\frac{n}{2}^k)+\omega_n^kA_1(\omega_\frac{n}{2}^k)\\
A(\omega_n^{k+\frac{n}{2}})=A_0(\omega_\frac{n}{2}^k)-\omega_n^kA_1(\omega_\frac{n}{2}^k)
\]

傅里叶逆变换

在单位复数根处插值

矩阵证明略

用\(\omega_n^{-1}\)代替\(\omega_n\),计算结果每个元素除以\(n\)即可


实现

\(\omega\)可以预处理也可以递推,预处理精度更高

递归结束时每个元素所在的位置就是“二进制翻转”的位置,可以非递归的实现fft

加倍次数界,两个次数界为n的多项式相乘,次数界为2n-1,加倍到第一个大于等于的2的幂




注意:

  1. 我传入的参数是次数界n,最高次数n-1,数组中用0到n-1表示
  2. 取整用floor向下取整,类型转换是向0取整



Fast Number-Theoretic Transform

生成子群 & 原根

子群:

\(群(S,\oplus),\ (S',\oplus),\ 满足S' \subset S,则(S',\oplus)是(S,\oplus)的子群\)


拉格朗日定理:

\(|S'| \mid |S|\)

证明需要用到陪集,得到陪集大小等于子群大小,每个陪集要么不想交要么相等,所有陪集的并是集合S,那么显然成立。


生成子群

\(a \in S\)的生成子群\(<a>=\{a^{(k)}:\ k\ge 1\}\),\(a\)是\(<a>\)的生成元


阶:

群\(S\)中\(a\)的阶是满足\(a^r=e\)的最小的r,符号\(ord(a)\)

\(ord(a)=|<a>|\),显然成立


考虑群\(Z_n^*=\{[a]_n \in Zn:gcd(a,n)=1\},\ |Z_n^*| = \phi(n)\)

阶就是满足\(a^{r} \equiv 1 \pmod n\)的最小的\(r,\ ord(a)=r\)

原根

\(g满足ord_n(g)=|Z_n^*|=\phi(n)\),对于质数\(p\),也就是说\(g^i \mod p, 0\le i <p\)结果互不相同

模n有原根的充要条件 \(n=2,4,p^e,2p^e\)

离散对数

\(g^t \equiv a \pmod n,\ ind_{n,g}(a)=t\)

因为g是原根,所以\(g^t\)每\(\phi(n)\)是一个周期,可以取到\(|Z_n^*|\)的所有元素

对于n是质数时,就是得到\([1,n-1]\)的所有数,就是\([0,n-2]\)到\([1,n-1]\)的映射

离散对数满足对数的相关性质,如\(ind(ab)\equiv ind(a)+ind(b) \pmod {n-1}\)

求原根

可以证明满足\(g^{r} \equiv 1 \pmod p\)的最小的r一定是\(p-1\)的约数

对于质数\(p\),质因子分解\(p-1\),若\(g^{\frac{p-1}{p_i}} \neq 1 \pmod p\)恒成立,g为p的原根

NTT

对于质数\(p=qn+1,\ n=2^m\),原根\(g\),则\(g^{qn} \equiv 1 \pmod p\)

将\(g_n=g^{q} \pmod p\)看做\(w_n\)的等价,满足\(w_n\)类似的性质,如:

  • \(g_n^n \equiv 1 \pmod p,\ g_n^{\frac{n}{2}} \equiv -1 \pmod p\)

这里的n(用N表示吧)可以比原来那个的n(乘法结果的长度扩展到2的幂次后的n)大,只要把\(\frac{qN}{n}\)看做q就行了

常见的\(p=1004535809=479 \cdot 2^{21} + 1,\ g=3,\quad p=998244353= 2 * 17 * 2^{23} + 1,\ g=3 ​\)

实现

\(g^{qn}\)就是\(e^{2\pi i}\)的等价,迭代到长度\(l\)时,\(g_l=g^{\frac{p-1}{l}}\)

或者\(w_n=g_l=g_n^{\frac{n}{l}}=g^{\frac{p-1}{l}}\)



***
这里放一个大整数相乘的模板
```cpp
//fft
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int N=(1'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&cstruct meow{

double x, y;

meow(double a=0, double b=0):x(a), y(b){}

};

meow operator +(meow a, meow b) {return meow(a.x+b.x, a.y+b.y);}

meow operator -(meow a, meow b) {return meow(a.x-b.x, a.y-b.y);}

meow operator (meow a, meow b) {return meow(a.xb.x-a.yb.y, a.xb.y+a.y*b.x);}

meow conj(meow a) {return meow(a.x, -a.y);}

typedef meow cd;

struct FastFourierTransform {

int n, rev[N];

cd omega[N], omegaInv[N];

void ini(int lim) {

n=1; int k=0;

while(n<lim) n<<=1, k++;

for(int i=0; i<n; i++) rev[i] = (rev[i>>1]>>1) | ((i&1)<<(k-1));

	for(int k=0; k<n; k++) {
omega[k] = cd(cos(2*PI/n*k), sin(2*PI/n*k));
omegaInv[k] = conj(omega[k]);
}
}
void fft(cd *a, cd *w) {
for(int i=0; i<n; i++) if(i<rev[i]) swap(a[i], a[rev[i]]);
for(int l=2; l<=n; l<<=1) {
int m=l>>1;
for(cd *p=a; p!=a+n; p+=l)
for(int k=0; k<m; k++) {
cd t = w[n/l*k] * p[k+m];
p[k+m]=p[k]-t;
p[k]=p[k]+t;
}
}
}
void dft(cd *a, int flag) {
if(flag==1) fft(a, omega);
else {
fft(a, omegaInv);
for(int i=0; i<n; i++) a[i].x/=n;
}
}
void mul(cd *a, cd *b, int m) {
ini(m);
dft(a, 1); dft(b, 1);
for(int i=0; i<n; i++) a[i]=a[i]*b[i];
dft(a, -1);
}

}f;

int n1, n2, m, c[N];

cd a[N], b[N];

char s1[N], s2[N];

int main() {

freopen("in","r",stdin);

scanf("%s%s",s1,s2);

n1=strlen(s1); n2=strlen(s2);

for(int i=0; i<n1; i++) a[i].x = s1[n1-i-1]-'0';

for(int i=0; i<n2; i++) b[i].x = s2[n2-i-1]-'0';

m=n1+n2-1;

f.mul(a, b, m);

for(int i=0; i<m; i++) c[i]=floor(a[i].x+0.5);

for(int i=0; i<m; i++) c[i+1]+=c[i]/10, c[i]%=10;

if(c[m]) m++;

for(int i=m-1; i>=0; i--) printf("%d",c[i]);

}


```cpp
//ntt
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const int N=(1<<18)+5, INF=1e9;
const double PI=acos(-1);
inline int read(){
char c=getchar();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
} ll P=1004535809;
ll Pow(ll a, ll b,ll P) {
ll ans=1;
for(; b; b>>=1, a=a*a%P)
if(b&1) ans=ans*a%P;
return ans;
}
struct NumberTheoreticTransform {
int n, rev[N];
ll g;
void ini(int lim) {
g=3;
n=1; int k=0;
while(n<lim) n<<=1, k++;
for(int i=0; i<n; i++) rev[i] = (rev[i>>1]>>1) | ((i&1)<<(k-1));
}
void dft(ll *a, int flag) {
for(int i=0; i<n; i++) if(i<rev[i]) swap(a[i], a[rev[i]]);
for(int l=2; l<=n; l<<=1) {
int m=l>>1;
ll wn = Pow(g, flag==1 ? (P-1)/l : P-1-(P-1)/l, P);
for(ll *p=a; p!=a+n; p+=l) {
ll w=1;
for(int k=0; k<m; k++) {
ll t = w * p[k+m]%P;
p[k+m]=(p[k]-t+P)%P;
p[k]=(p[k]+t)%P;
w=w*wn%P;
}
}
}
if(flag==-1) {
ll inv=Pow(n, P-2, P);
for(int i=0; i<n; i++) a[i]=a[i]*inv%P;
}
}
void mul(ll *a, ll *b, int m) {
ini(m);
dft(a, 1); dft(b, 1);
for(int i=0; i<n; i++) a[i]=a[i]*b[i];
dft(a, -1);
}
}f; int n1, n2, m, c[N];
ll a[N], b[N];
char s1[N], s2[N];
int main() {
freopen("in","r",stdin);
scanf("%s%s",s1,s2);
n1=strlen(s1); n2=strlen(s2);
for(int i=0; i<n1; i++) a[i] = s1[n1-i-1]-'0';
for(int i=0; i<n2; i++) b[i] = s2[n2-i-1]-'0';
m=n1+n2-1;
f.mul(a, b, m);
for(int i=0; i<m; i++) c[i]=a[i];
for(int i=0; i<m; i++) c[i+1]+=c[i]/10, c[i]%=10;
if(c[m]) m++;
for(int i=m-1; i>=0; i--) printf("%d",c[i]);
}

快速傅里叶变换 & 快速数论变换的更多相关文章

  1. 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】

    原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...

  2. 快速傅里叶变换FFT& 数论变换NTT

    相关知识 时间域上的函数f(t)经过傅里叶变换(Fourier Transform)变成频率域上的F(w),也就是用一些不同频率正弦曲线的加 权叠加得到时间域上的信号. \[ F(\omega)=\m ...

  3. 模板 - 数学 - 快速傅里叶变换/快速数论变换(FFT/NTT)

    先看看. 通常模数常见的有998244353,1004535809,469762049,这几个的原根都是3.所求的项数还不能超过2的23次方(因为998244353的分解). 感觉没啥用. #incl ...

  4. 从傅里叶变换(FFT)到数论变换(NTT)

    FFT可以用来计算多项式乘法,但是复数的运算中含有大量的浮点数,精度较低.对于只有整数参与运算的多项式,有时,\(\text{NTT(Number-Theoretic Transform)}\)会是更 ...

  5. 快速傅里叶变换(FFT)相关内容汇总

    (原稿:https://paste.ubuntu.com/p/yJNsn3xPt8/) 快速傅里叶变换,是求两个多项式卷积的算法,其时间复杂度为$O(n\log n)$,优于普通卷积求法,且根据有关证 ...

  6. Algorithm: 多项式乘法 Polynomial Multiplication: 快速傅里叶变换 FFT / 快速数论变换 NTT

    Intro: 本篇博客将会从朴素乘法讲起,经过分治乘法,到达FFT和NTT 旨在能够让读者(也让自己)充分理解其思想 模板题入口:洛谷 P3803 [模板]多项式乘法(FFT) 朴素乘法 约定:两个多 ...

  7. 【算法】快速数论变换(NTT)初探

    [简介] 快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论 ...

  8. Python 实现图像快速傅里叶变换和离散余弦变换

    图像的正交变换在数字图像的处理与分析中起着很重要的作用,被广泛应用于图像增强.去噪.压缩编码等众多领域.本文手工实现了二维离散傅里叶变换和二维离散余弦变换算法,并在多个图像样本上进行测试,以探究二者的 ...

  9. 「算法笔记」快速数论变换(NTT)

    一.简介 前置知识:多项式乘法与 FFT. FFT 涉及大量 double 类型数据操作和 \(\sin,\cos\) 运算,会产生误差.快速数论变换(Number Theoretic Transfo ...

随机推荐

  1. 2017ecjtu-summer training #4 UESTC 1584

    此题链接 http://acm.uestc.edu.cn/#/problem/show/1584 此题和hdu1541几乎完全一样,我们要先对坐标排序,再进行操作. hdu1541题解 http:// ...

  2. HDU--2024

    C语言合法标识符 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total S ...

  3. Coins、Tokens、山寨币:区别在哪里

    [译] Coins, Tokens & Altcoins: What's the Difference? coin:指"正宗的"数字货币,比如比特币 tokens:比如用 ...

  4. SpringMVC整合Shiro权限框架

    尊重原创:http://blog.csdn.net/donggua3694857/article/details/52157313 最近在学习Shiro,首先非常感谢开涛大神的<跟我学Shiro ...

  5. 程序员之殇 —— (The Beginning of the End)噩梦、崩坏

    Look at all those faces out there (当我环视周遭的一张张脸孔) We are so different(我们是如此的不同) But we have one thing ...

  6. layui之事件监听(table)

    这几天在学习layui,感觉这框架挺好用的,前后端都适用,许多原本比较复杂的东西用该框架很容易就能实现. 今天看了table里的事件监听这个知识点. 语法:table.on('event(filter ...

  7. 安装Ruby、Sass与Compass

    sass基于Ruby语言开发而成,因此安装sass前需要安装Ruby.(注:mac下自带Ruby无需在安装Ruby!) window下安装SASS首先需要安装Ruby,先从官网下载Ruby并安装.安装 ...

  8. eclipse配置虚拟路径后,每次启动tomcat都会虚拟路径失效的问题解决

    由于,eclipse启动tomcat部署项目并不是直接把项目放到tomcat的webapps目录下的,而是从我们在eclipse配置的外部tomcat中取出二进制文件,在eclipse内部插件中作为t ...

  9. dig命令

      dig(域信息搜索器)命令是一个用于询问 DNS 域名服务器的灵活的工具.它执行 DNS 搜索,显示从受请求的域名服务器返回的答复.多数 DNS 管理员利用 dig 作为 DNS 问题的故障诊断, ...

  10. Python 3 利用机器学习模型 进行手写体数字识别

    0.引言 介绍了如何生成数据,提取特征,利用sklearn的几种机器学习模型建模,进行手写体数字1-9识别. 用到的四种模型: 1. LR回归模型,Logistic Regression 2. SGD ...