【简介】

  快速傅里叶变换(FFT)运用了单位复根的性质减少了运算,但是每个复数系数的实部和虚部是一个余弦和正弦函数,因此系数都是浮点数,而浮点数的运算速度较慢且可能产生误差等精度问题,因此提出了以数论为基础的具有循环卷积性质的快速数论变换(NTT)。

  在FFT中,通过$n$次单位复根即$\omega^n=1$的$\omega$来运算,而对于NTT来说,则是运用了素数的原根来运算。

【原根】

【定义】

  对于两个正整数$a,m$满足$gcd(a, m)=1$,由欧拉定理可知,存在正整数$d\leq m-1$,如$d=\varphi(m)$,使得$a^d\equiv 1(mod\ m)$。

  因此,在$gcd(a, m)=1$时,定义$a$对模$m$的指数$\delta m(a)$为使$a^d\equiv 1(mod\ m)$成立的最小正整数$d$。若$\delta m(a)=\varphi (m)$,则称$a$是模$m$的原根。

【性质/定义2】

  若一个数$g$是对于$P$的原根,那么$g^i\ mod\ P,1\leq i<P$的结果互不相同。

【求原根方法】

  对质数$P-1$分解质因数得到不同的质因子$p_1,p_2,p_3,...,p_n$,对于任何$2\leq a\leq P-1$,判定$a$是否为$P$的原根,只需要检验$a^{\frac{P-1}{p_1}},a^{\frac{P-1}{p_2}},...,a^{\frac{P-1}{p_n}}$这n个数中,是否存在一个数$mod\ P$为$1$,若存在,则$a$不是$P$的原根,否则$a$是$P$的原根。

【正确性证明】

  假设存在一个$t<\varphi(P)=P-1$使得$a^t\equiv 1(mod\ P)$且$\forall i\subseteq [1,P),a^{\frac{P-1}{p_i}}\ mod\ P \neq 1$。

  由裴蜀定理得,一定存在一组$k,x$使得$kt=(P-1)x+gcd(t, P-1)$

  由欧拉定理/费马小定理得,$a^{P-1}\equiv 1(mod\ P)$

  于是$1\equiv a^{kt}\equiv a^{(P-1)x+gcd(t, P-1)}\equiv a^{gcd(t, P-1)}(mod\ P)$

  $t<P-1$故$gcd(t, P-1)<P-1$

  又$gcd(t, P-1)|P-1$,于是$gcd(t, P-1)$必整除$a^{\frac{P-1}{p_1}},a^{\frac{P-1}{p_2}},...,a^{\frac{P-1}{p_n}}$中至少一个,设$gcd(t, P-1)|a^{\frac{P-1}{p_i}}$,则$a^{\frac{P-1}{p_i}}\equiv a^{gcd(t, P-1)}\equiv 1(mod\ P)$。

  故假设不成立。

【用途】

  我们可以发现原根$g$拥有所有FFT所需的$\omega$的性质,于是如果我们用$g^{\frac{P-1}{N}}(mod\ P)$来代替$\omega_n=e^{\frac{2\pi i}{N}}$,就能把复数对应成一个整数,在$(mod\ P)$意义下做快速变换了。

【NTT模数】

  显然在上述的用途中,$P$必须是素数且$N$必须是$P-1$的因数,因为$N$是$2$的幂,所以可以构造形如$P=c\cdot 2^k+1$的素数。

  常见的形如$P=c\cdot 2^k+1$的素数有$998244353=119\cdot 2^{23}+1,1004535809=479\cdot 2^{21}+1$,它们的原根都为$3$

  如果题目的模数$P$任意怎么办?我们取的模数必须超过$n(P-1)^2$

  那么我们可以取多个模数(乘积$> n(P-1)^2$)做完NTT之后用CRT合并...

  模数表:http://blog.miskcoo.com/2014/07/fft-prime-table

【例题】

例1:bzoj2179: FFT快速傅立叶

  原来的配方,熟悉的味道...(怎么比FFT慢了

  1. #include<iostream>
  2. #include<cstring>
  3. #include<cstdlib>
  4. #include<cstdio>
  5. #include<algorithm>
  6. #define ll long long
  7. #define MOD(x) ((x)>=mod?(x)-mod:(x))
  8. using namespace std;
  9. const int maxn=, inf=1e9, mod=;
  10. int n, N;
  11. int a[maxn], b[maxn], c[maxn];
  12. char s[maxn];
  13. inline int power(int a, int b)
  14. {
  15. int ans=;
  16. for(;b;b>>=, a=1ll*a*a%mod)
  17. if(b&) ans=1ll*ans*a%mod;
  18. return ans;
  19. }
  20. inline void ntt(int *a, int f)
  21. {
  22. for(int i=, j=;i<N;i++)
  23. {
  24. if(i<j) swap(a[i], a[j]);
  25. for(int k=N>>;(j^=k)<k;k>>=);
  26. }
  27. for(int i=;i<=N;i<<=)
  28. {
  29. int nw=power(, (mod-)/i);
  30. if(f==-) nw=power(nw, mod-);
  31. for(int j=, m=i>>;j<N;j+=i)
  32. for(int k=, w=;k<m;k++)
  33. {
  34. int t=1ll*a[j+k+m]*w%mod;
  35. a[j+k+m]=MOD(a[j+k]-t+mod);
  36. a[j+k]=MOD(a[j+k]+t);
  37. w=1ll*w*nw%mod;
  38. }
  39. }
  40. if(f==-) for(int i=, inv=power(N, mod-);i<N;i++) a[i]=1ll*a[i]*inv%mod;
  41. }
  42. int main()
  43. {
  44. scanf("%d", &n); for(N=;N<(n<<);N<<=);
  45. scanf("%s", s); for(int i=;i<n;i++) a[n-i-]=s[i]-'';
  46. scanf("%s", s); for(int i=;i<n;i++) b[n-i-]=s[i]-'';
  47. ntt(a, ); ntt(b, );
  48. for(int i=;i<N;i++) c[i]=1ll*a[i]*b[i]%mod;
  49. ntt(c, -);
  50. for(int i=;i<N;i++)
  51. if(c[i]>=)
  52. {
  53. c[i+]+=c[i]/; c[i]%=;
  54. if(i==N-) N++;
  55. }
  56. N--; while(!c[N] && N>) N--;
  57. for(int i=N;~i;i--) printf("%d", c[i]);
  58. }

例2:bzoj3992: [SDOI2015]序列统计

  DP。

  $f[i][j]$为前$i$个数总和$mod\ m$后为$j$的方案数,$B[i]$为集合内数$mod\ m$后为$i$的数的个数。$$f[i][j*k\%m]=\sum f[i-1][j]*B[k]$$

  这个式子显然是可以矩阵快速幂优化的,效率$O(m^2logn)$,还是会TLE。

  对所有数$x$,设$g^i(mod\ m)=x$,则将$x$映射为$i$,即取$x$在模$m$意义下的离散对数,$c$为$f[L]$,$a$为$f[L-1]$。

  原式改为:$$c[(i+j)\%(\varphi(m)=m-1)]=\sum_{k=0}^{i+j} a[i]\cdot b[i+j-k]$$

  卷积形式,上NTT

  注意乘出来超过$m-1$项,而次数超过$m-1$项的贡献应加回次数减去$m-1$的项中,即上方式子的取模操作。

  NTT次数较多预处理出$g$的所有次幂会快一些,而且因为不明原因直接取模会比快速取模快,CPU你怎么这么难伺候...

  1. #include<iostream>
  2. #include<cstring>
  3. #include<cstdlib>
  4. #include<cstdio>
  5. #define MOD(x) ((x)>=mod?(x)-mod:(x))
  6. #include<algorithm>
  7. #define ll long long
  8. using namespace std;
  9. const int maxn=, inf=1e9, mod=;
  10. int n, m, x, s, N, g, X;
  11. int a[maxn], b[maxn], A[maxn], B[maxn], ind[maxn], p[maxn], g1[maxn], g2[maxn];
  12. inline void read(int &k)
  13. {
  14. int f=; k=; char c=getchar();
  15. while(c<'' || c>'') c=='-'&&(f=-), c=getchar();
  16. while(c<='' && c>='') k=k*+c-'', c=getchar();
  17. k*=f;
  18. }
  19. inline int power(int a, int b, int p)
  20. {
  21. int ans=;
  22. for(;b;b>>=, a=1ll*a*a%p)
  23. if(b&) ans=1ll*ans*a%p;
  24. return ans;
  25. }
  26. inline bool judge(int x)
  27. {
  28. for(int i=;i<=p[];i++)
  29. if(power(x, (m-)/p[i], m)==) return ;
  30. return ;
  31. }
  32. inline int yg(int x)
  33. {
  34. x--;
  35. for(int i=;i*i<=x;i++)
  36. if(x%i==)
  37. {
  38. p[++p[]]=i;
  39. while(x%i==) x/=i;
  40. }
  41. if(x>) p[++p[]]=x;
  42. for(int i=;;i++) if(judge(i)) return i;
  43. }
  44. inline void init()
  45. {
  46. int base1=power(, (mod-)/N, mod), base2=power(base1, mod-, mod);
  47. g1[]=g2[]=;
  48. for(int i=;i<=N;i++) g1[i]=1ll*g1[i-]*base1%mod, g2[i]=1ll*g2[i-]*base2%mod;
  49. }
  50. inline void ntt(int *a, int *w, int f)
  51. {
  52. for(int i=, j=;i<N;i++)
  53. {
  54. if(i<j) swap(a[i], a[j]);
  55. for(int k=N>>;(j^=k)<k;k>>=);
  56. }
  57. for(int i=;i<=N;i<<=)
  58. for(int j=, m=i>>;j<N;j+=i)
  59. for(int k=;k<m;k++)
  60. {
  61. int t=1ll*a[j+k+m]*w[N/i*k]%mod;
  62. a[j+k+m]=(a[j+k]-t+mod)%mod;
  63. a[j+k]=(a[j+k]+t)%mod;
  64. }
  65. if(f==-) for(int i=, inv=power(N, mod-, mod);i<N;i++) a[i]=1ll*a[i]*inv%mod;
  66. }
  67. inline void mul(int *a, int *b, int *c)
  68. {
  69. memcpy(A, a, N<<); memcpy(B, b, N<<);
  70. ntt(A, g1, ); ntt(B, g1, );
  71. for(int i=;i<N;i++) c[i]=1ll*A[i]*B[i]%mod;
  72. ntt(c, g2, -);
  73. for(int i=;i<m-;i++) c[i]=MOD(c[i]+c[i+m-]), c[i+m-]=;
  74. }
  75. int main()
  76. {
  77. read(n); read(m); read(X); read(s); g=yg(m);
  78. for(int i=, j=;i<m-;i++, j=1ll*j*g%m) ind[j]=i; X=ind[X];
  79. for(N=;N<m;N<<=); N<<=; init();
  80. for(int i=;i<=s;i++) read(x), x && (a[ind[x]]++); b[]=;
  81. for(;n;n>>=, mul(a, a, a)) if(n&) mul(a, b, b);
  82. printf("%d\n", b[X]);
  83. }

例3:

【算法】快速数论变换(NTT)初探的更多相关文章

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

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

  2. JZYZOJ 2041 快速数论变换 NTT 多项式

    http://172.20.6.3/Problem_Show.asp?id=2041 https://blog.csdn.net/ggn_2015/article/details/68922404 代 ...

  3. [快速数论变换 NTT]

    先粘一个模板.这是求高精度乘法的 #include <bits/stdc++.h> #define maxn 1010 using namespace std; char s[maxn]; ...

  4. 快速数论变换(NTT)小结

    NTT 在FFT中,我们需要用到复数,复数虽然很神奇,但是它也有自己的局限性--需要用double类型计算,精度太低 那有没有什么东西能够代替复数且解决精度问题呢? 这个东西,叫原根 原根 阶 若\( ...

  5. 模板 - 数学 - 多项式 - 快速数论变换/NTT

    Huffman分治的NTT,常数一般.使用的时候把多项式的系数们放进vector里面,然后调用solve就可以得到它们的乘积.注意这里默认最大长度是1e6,可能需要改变. #include<bi ...

  6. 快速数论变换NTT模板

    51nod 1348 乘积之和 #include <cmath> #include <iostream> #include <cstdio> #include &l ...

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

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

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

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

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

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

随机推荐

  1. ajax中的application/x-www-form-urlencoded中的使用[转]

    一,HTTP上传的基本知识 在Form元素的语法中,EncType表明提交数据的格式 用 Enctype 属性指定将数据回发到服务器时浏览器使用的编码类型.下边是说明: application/x-w ...

  2. Go 数组(1)

    1.一旦声明,数组里存储的数据类型和数组长度就都不能改变了.如果需要存储更多的元素, 就需要先创建一个更长的数组,再把原来数组里的值复制到新数组里. 例如: ]int 2.使用数组字面量声明数组 // ...

  3. python每日练习

    """ 习题 1:一个列表,排重,不能用 set,也不能用字典 """ #方法一1:循环.遍历 l = [1,1,1,2,2,3,4,4,6 ...

  4. flask 根路由在蓝图中

  5. Git使用包括切换分支

  6. springMVC带参数请求重定向

    SpirngMVC返回逻辑视图名 可以分下面几种情况: 1. servlet进行请求转发,返回到jsp页面,如  return "index.jsp" ; 2. servlet 返 ...

  7. 知道一个数组某个index对应的值 不知道下标的情况下删除该值

    for (index,item) in Arr.enumerated() { if item == item { Arr.remove(at: index) } } 更好的方法是用数组的filter尾 ...

  8. springmvc对象作为 目标方法的参数。

    /** * Spring MVC 会按请求参数名和 POJO 属性名进行自动匹配, 自动为该对象填充属性值.支持级联属性. * 如:dept.deptId.dept.address.tel 等 */ ...

  9. AshMap如何让hash保持一致

    学Java的都知道hashMap的底层是“链表散列”的数据结构也也可以说是hash表.在put的实话先根据key的hashcode重新计算hash值的,而我们又知道hash是一种算法.所以哈希码并不是 ...

  10. [CF1168D]Anagram Paths

    题意:给一棵\(n\)个节点的二叉树,每条边上有一个小写字母或者\(?\),\(q\)次修改操作,每次修改某条边上的字符,问修改后是否存在一种方案,使得给所有\(?\)填上小写字母后,所有叶子到根的路 ...