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


[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));

  1. for(int k=0; k<n; k++) {
  2. omega[k] = cd(cos(2*PI/n*k), sin(2*PI/n*k));
  3. omegaInv[k] = conj(omega[k]);
  4. }
  5. }
  6. void fft(cd *a, cd *w) {
  7. for(int i=0; i<n; i++) if(i<rev[i]) swap(a[i], a[rev[i]]);
  8. for(int l=2; l<=n; l<<=1) {
  9. int m=l>>1;
  10. for(cd *p=a; p!=a+n; p+=l)
  11. for(int k=0; k<m; k++) {
  12. cd t = w[n/l*k] * p[k+m];
  13. p[k+m]=p[k]-t;
  14. p[k]=p[k]+t;
  15. }
  16. }
  17. }
  18. void dft(cd *a, int flag) {
  19. if(flag==1) fft(a, omega);
  20. else {
  21. fft(a, omegaInv);
  22. for(int i=0; i<n; i++) a[i].x/=n;
  23. }
  24. }
  25. void mul(cd *a, cd *b, int m) {
  26. ini(m);
  27. dft(a, 1); dft(b, 1);
  28. for(int i=0; i<n; i++) a[i]=a[i]*b[i];
  29. dft(a, -1);
  30. }

}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]);

}


  1. ```cpp
  2. //ntt
  3. #include <iostream>
  4. #include <cstdio>
  5. #include <cstring>
  6. #include <algorithm>
  7. #include <cmath>
  8. using namespace std;
  9. typedef long long ll;
  10. const int N=(1<<18)+5, INF=1e9;
  11. const double PI=acos(-1);
  12. inline int read(){
  13. char c=getchar();int x=0,f=1;
  14. while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
  15. while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
  16. return x*f;
  17. }
  18. ll P=1004535809;
  19. ll Pow(ll a, ll b,ll P) {
  20. ll ans=1;
  21. for(; b; b>>=1, a=a*a%P)
  22. if(b&1) ans=ans*a%P;
  23. return ans;
  24. }
  25. struct NumberTheoreticTransform {
  26. int n, rev[N];
  27. ll g;
  28. void ini(int lim) {
  29. g=3;
  30. n=1; int k=0;
  31. while(n<lim) n<<=1, k++;
  32. for(int i=0; i<n; i++) rev[i] = (rev[i>>1]>>1) | ((i&1)<<(k-1));
  33. }
  34. void dft(ll *a, int flag) {
  35. for(int i=0; i<n; i++) if(i<rev[i]) swap(a[i], a[rev[i]]);
  36. for(int l=2; l<=n; l<<=1) {
  37. int m=l>>1;
  38. ll wn = Pow(g, flag==1 ? (P-1)/l : P-1-(P-1)/l, P);
  39. for(ll *p=a; p!=a+n; p+=l) {
  40. ll w=1;
  41. for(int k=0; k<m; k++) {
  42. ll t = w * p[k+m]%P;
  43. p[k+m]=(p[k]-t+P)%P;
  44. p[k]=(p[k]+t)%P;
  45. w=w*wn%P;
  46. }
  47. }
  48. }
  49. if(flag==-1) {
  50. ll inv=Pow(n, P-2, P);
  51. for(int i=0; i<n; i++) a[i]=a[i]*inv%P;
  52. }
  53. }
  54. void mul(ll *a, ll *b, int m) {
  55. ini(m);
  56. dft(a, 1); dft(b, 1);
  57. for(int i=0; i<n; i++) a[i]=a[i]*b[i];
  58. dft(a, -1);
  59. }
  60. }f;
  61. int n1, n2, m, c[N];
  62. ll a[N], b[N];
  63. char s1[N], s2[N];
  64. int main() {
  65. freopen("in","r",stdin);
  66. scanf("%s%s",s1,s2);
  67. n1=strlen(s1); n2=strlen(s2);
  68. for(int i=0; i<n1; i++) a[i] = s1[n1-i-1]-'0';
  69. for(int i=0; i<n2; i++) b[i] = s2[n2-i-1]-'0';
  70. m=n1+n2-1;
  71. f.mul(a, b, m);
  72. for(int i=0; i<m; i++) c[i]=a[i];
  73. for(int i=0; i<m; i++) c[i+1]+=c[i]/10, c[i]%=10;
  74. if(c[m]) m++;
  75. for(int i=m-1; i>=0; i--) printf("%d",c[i]);
  76. }

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

  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. Spark环境搭建(下)——Spark安装

    1. 下载Spark 1.1 官网下载Spark http://spark.apache.org/downloads.html 打开上述链接,进入到下图,点击红框下载Spark-2.2.0-bin-h ...

  2. SpringMVC框架学习笔记(2)——使用注解开发SpringMVC

    1.配置web.xml <servlet> <servlet-name>mvc</servlet-name> <servlet-class>org.sp ...

  3. leak finder

    介绍 leak finder 是google开源团队发布了一个新的可以帮助web应用程序开发者在他们的JavaScript程序中找出内存泄露问题的工具: http://feedproxy.google ...

  4. Core Animation 文档翻译(第三篇)

    Core Animation 文档翻译(第三篇) 设置Layer对象 当我们使用核心动画时,Layer对象是一切的核心.Layers 管理我们APP的可视化content,Layer也提供了conte ...

  5. git生成sshkey

  6. :nth-child(n)

    规定属于其父元素的第二个子元素的每个 p 的背景色: p:nth-child(2) { background:#ff0000; } 1定义和用法 :nth-child(n) 选择器匹配属于其父元素的第 ...

  7. 将本地的项目导入到github仓库总结lxw

    关键步骤: 第一:git clone https://github.com/lxw18231857001/demo-.git           #把github上面的仓库克隆到本地 本地项目文件夹下 ...

  8. ASP.NET Core下发布网站

    一.windows下发布到IIS 1.前奏:IIS上的准备 (1)IIS 必须安装AspNetCoreModule 模块 下载地址:(DotNetCore.2.0.3-WindowsHosting-a ...

  9. pthread_cond_wait的spurious wakeup问题

    最近在温习pthread的时候,忽然发现以前对pthread_cond_wait的了解太肤浅了.昨晚在看<Programming With POSIX Threads>的时候,看到了pth ...

  10. CCF系列之字符串匹配(201409-3)

    试题编号:201409-3试题名称:字符串匹配时间限制: 1.0s内存限制: 256.0MB 问题描述 给出一个字符串和多行文字,在这些文字中找到字符串出现的那些行.你的程序还需支持大小写敏感选项:当 ...