「学习笔记」FFT 之优化——NTT

前言

\(NTT\) 在某种意义上说,应该属于 \(FFT\) 的一种优化。

——因而必备知识肯定要有 \(FFT\) 啦...

如果不知道 \(FFT\) 的大佬可以走这里

引入

在 \(FFT\) 中,为了能计算单位原根 \(\omega\) ,我们使用了 \(\text{C++}\) 的 math 库中的 \(cos、sin\) 函数,所以我们无法避免地使用了 double 以及其运算。

但是,众所周知的, double 的运算很慢,并且,我们的虚数乘法是类似于下面这种打法:

  1. cplx operator * (const cplx a)const{return cplx(vr*a.vr-vi*a.vi,vr*a.vi+a.vr*vi);}

显然,一次虚数乘法涉及四次 double 的乘法。

并且在运算过程中,会有大量的精度丢失,这都是我们不可接受的。

然而问题来了:我们多项式乘法都是整数在那里搞来搞去,为什么一定要扯到浮点数。是否存在一个在模意义下的,只使用整数的方法?——Tiw_Air_OAO

快速数论变换——NTT

想一想我们使用了单位复根的哪些特性:

  1. \(w_{n}^{i}*w_{n}^{j}=w_{n}^{i+j}\)
  2. \(w_{dn}^{dk}=w_n^k\)
  3. \(w_{2n}^k=-w_{2n}^{k+n}\)
  4. \(n\) 个单位根互不相同,且 \(w_n^0=1\)

那么我们能否在 模意义 下找到一个性质相同的数?

这里有一个同样也是 某某根 的东西,叫做 原根

对于素数 \(p\) , \(p\) 的原根 \(G\) 定义为使得 \(G^0,G^1,...,G^{p−2}(mod\space p)\) 互不相同的数。

仔细思考一下,发现 原根单位复根 很像。

同理,我们再定义 \(g_n^k = (G^{\frac{p-1}{n}})^k\) ,这样 \(g_n^k\) 就与 \(\omega_n^k\) 长得更像了...

但是,必须在 \(g_n^k\) 满足与 \(\omega_n^k\) 同样的性质时,我们才能等价替换。

现在,我们检验原根在模意义下是否满足与单位复根同样的性质:

  1. 由幂的运算立即可得
  2. 由幂的运算立即可得
  3. \(g_{2n}^{k+n}=(G^{\frac{p-1}{2n}})^{k+n}=(G^{\frac{p-1}{2n}})^k*(G^{\frac{p-1}{2n}})^n=G^{\frac{p-1}{2}}*g_{2n}^k=-g_{2n}^k(mod\space p)\) ,因为 \((G^{p-1}=1(mod\space p)\) 且由原根定义 \(G^{\frac{p-1}{2}}\not=G^{p-1}(mod\space p)\) ,故 \(G^{\frac{p-1}{2}}=-1(mod\space p)\)
  4. 由原根的定义立即可得

发现原根可以在模意义下 完全替换 单位复根。

这就是 \(NTT\) 了。

但是,这样的方法对模数会有一定的限制

令 \(m = 2^p*k+1\) , \(k\) 为奇数,则多项式长度必须 \(n \le 2^p\)

至于模数以及其原根,没有必要来死记,为什么?

我们程序员就应该干我们经常干的事情——打表可得...

以下是参考代码:

  1. #include<cstdio>
  2. #include<algorithm>
  3. using namespace std;
  4. #define rep(i,__l,__r) for(register int i=__l,i##_end_=__r;i<=i##_end_;++i)
  5. #define fep(i,__l,__r) for(register int i=__l,i##_end_=__r;i>=i##_end_;--i)
  6. #define writc(a,b) fwrit(a),putchar(b)
  7. #define mp(a,b) make_pair(a,b)
  8. #define ft first
  9. #define sd second
  10. #define LL long long
  11. #define ull unsigned long long
  12. #define pii pair<int,int>
  13. #define Endl putchar('\n')
  14. // #define FILEOI
  15. // #define int long long
  16. #ifdef FILEOI
  17. #define MAXBUFFERSIZE 500000
  18. inline char fgetc(){
  19. static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
  20. return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
  21. }
  22. #undef MAXBUFFERSIZE
  23. #define cg (c=fgetc())
  24. #else
  25. #define cg (c=getchar())
  26. #endif
  27. template<class T>inline void qread(T& x){
  28. char c;bool f=0;
  29. while(cg<'0'||'9'<c)f|=(c=='-');
  30. for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
  31. if(f)x=-x;
  32. }
  33. inline int qread(){
  34. int x=0;char c;bool f=0;
  35. while(cg<'0'||'9'<c)f|=(c=='-');
  36. for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
  37. return f?-x:x;
  38. }
  39. template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
  40. template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
  41. template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
  42. template<class T>inline T fab(const T x){return x>0?x:-x;}
  43. inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
  44. inline void getInv(int inv[],const int lim,const int MOD){
  45. inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
  46. }
  47. template<class T>void fwrit(const T x){
  48. if(x<0)return (void)(putchar('-'),fwrit(-x));
  49. if(x>9)fwrit(x/10);putchar(x%10^48);
  50. }
  51. inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
  52. return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
  53. }
  54. const int MAXN=3e6;
  55. const int MOD=998244353,g=3,gi=332748118;
  56. int n,m;
  57. int a[MAXN+5],b[MAXN+5],revi[MAXN+5];
  58. inline int qkpow(int a,int x){
  59. int ret=1;
  60. for(;x>0;x>>=1){
  61. if(x&1)ret=1ll*ret*a%MOD;
  62. a=1ll*a*a%MOD;
  63. }
  64. return ret;
  65. }
  66. inline void ntt(int* f,const short opt=1){
  67. for(int i=0;i<n;++i)if(i<revi[i])swap(f[i],f[revi[i]]);
  68. for(int p=2,len,gn,Pow,tmp;p<=n;p<<=1){
  69. len=p>>1,gn=qkpow(opt==1?g:gi,(MOD-1)/p);
  70. for(int k=0;k<n;k+=p){Pow=1;
  71. for(int l=k;l<k+len;++l,Pow=1ll*Pow*gn%MOD){
  72. tmp=1ll*Pow*f[len+l]%MOD;
  73. if(f[l]-tmp<0)f[len+l]=f[l]-tmp+MOD;
  74. else f[len+l]=f[l]-tmp;
  75. if(f[l]-MOD+tmp>0)f[l]=f[l]-MOD+tmp;
  76. else f[l]+=tmp;
  77. }
  78. }
  79. }
  80. if(opt==-1){
  81. int inv=qkpow(n,MOD-2);
  82. for(int i=0;i<n;++i)f[i]=1ll*f[i]*inv%MOD;
  83. }
  84. }
  85. inline void launch(){
  86. qread(n,m);
  87. rep(i,0,n)qread(a[i]);
  88. rep(i,0,m)qread(b[i]);
  89. for(m+=n,n=1;n<=m;n<<=1);
  90. for(int i=0;i<n;++i)revi[i]=(revi[i>>1]>>1)|((i&1)?n>>1:0);
  91. ntt(a),ntt(b);
  92. for(int i=0;i<n;++i)a[i]=1ll*a[i]*b[i]%MOD;
  93. ntt(a,-1);
  94. rep(i,0,m)writc(a[i],' ');
  95. Endl;
  96. }
  97. signed main(){
  98. #ifdef FILEOI
  99. freopen("file.in","r",stdin);
  100. freopen("file.out","w",stdout);
  101. #endif
  102. launch();
  103. return 0;
  104. }

一些引申问题及解决方法

假如题目中规定了模数怎么办?还卡 FFT 的精度怎么办?

有两种方法:三模数 NTT 以及 拆系数 FFT (MTT)

三模数 NTT

我们可以选取三个适用于 \(NTT\) 的模数 \(M1,M2,M3\) 进行 \(NTT\) ,用中国剩余定理合并得到 \(x\space mod\space (M1*M2*M3)\) 的值。只要保证 \(x < M1*M2*M3\) 就可以直接输出这个值。

之所以是三模数,因为用三个大小在 \(10^9\) 左右模数对于大部分题目来说就足够了。

但是 \(M1*M2*M3\) 可能非常大怎么办呢?难不成我还要写高精度?其实也可以。

我们列出同余方程组:

\[\begin{cases}
x \equiv a_1&\mod m_1\\
x \equiv a_2&\mod m_2\\
x \equiv a_3&\mod m_3\\
\end{cases}
\]

用中国剩余定理合并前两个方程组,得到:

\[\begin{cases}
x \equiv A&\mod M\\
x \equiv a_3&\mod m_3\\
\end{cases}
\]

其中的 \(M\) 满足 \(M = m1*m2 < 10^{18}\)

然后将第一个方程变形得到 \(x = kM + A\) ,代入第二个方程,得到:

\[kM+A \equiv a_3\mod m_3\\
k \equiv (a_3-A)*M^{-1} \mod m_3\\
\]

令 \(Q = (a_3-A)*M^{-1}\) ,则 \(k = Pm_3 + Q\) 。

再将上式代入回 \(x = kM + A\) ,得 \(x = (Pm_3 + Q)M+ A = Pm_3M+QM+A\) 。

又因为 \(M = m_1m_2\) ,所以 \(x = Pm_1m_2m_3 + QM + A\) 。

也就是说 \(x \equiv QM + A \mod m_1m_2m_3\) 。

然后,我们完美地解决了这个东西。

接下来是代码:

  1. #include<cstdio>
  2. #include<algorithm>
  3. using namespace std;
  4. #define rep(i,__l,__r) for(register int i=__l,i##_end_=__r;i<=i##_end_;++i)
  5. #define fep(i,__l,__r) for(register int i=__l,i##_end_=__r;i>=i##_end_;--i)
  6. #define writc(a,b) fwrit(a),putchar(b)
  7. #define mp(a,b) make_pair(a,b)
  8. #define ft first
  9. #define sd second
  10. #define LL long long
  11. #define ull unsigned long long
  12. #define pii pair<int,int>
  13. #define Endl putchar('\n')
  14. // #define FILEOI
  15. #define int long long
  16. #ifdef FILEOI
  17. #define MAXBUFFERSIZE 500000
  18. inline char fgetc(){
  19. static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
  20. return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
  21. }
  22. #undef MAXBUFFERSIZE
  23. #define cg (c=fgetc())
  24. #else
  25. #define cg (c=getchar())
  26. #endif
  27. template<class T>inline void qread(T& x){
  28. char c;bool f=0;
  29. while(cg<'0'||'9'<c)f|=(c=='-');
  30. for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
  31. if(f)x=-x;
  32. }
  33. inline int qread(){
  34. int x=0;char c;bool f=0;
  35. while(cg<'0'||'9'<c)f|=(c=='-');
  36. for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
  37. return f?-x:x;
  38. }
  39. template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
  40. template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
  41. template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
  42. template<class T>inline T fab(const T x){return x>0?x:-x;}
  43. inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
  44. inline void getInv(int inv[],const int lim,const int MOD){
  45. inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
  46. }
  47. template<class T>void fwrit(const T x){
  48. if(x<0)return (void)(putchar('-'),fwrit(-x));
  49. if(x>9)fwrit(x/10);putchar(x%10^48);
  50. }
  51. inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
  52. return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
  53. }
  54. inline int qkpow(int a,int n,const int mod){
  55. int ret=1;
  56. for(;n>0;n>>=1){
  57. if(n&1)ret=ret*a%mod;
  58. a=a*a%mod;
  59. }
  60. return ret;
  61. }
  62. const int MAXN=3e5;
  63. const int MOD[3]={469762049ll,998244353ll,1004535809ll};//三模数
  64. const int G=3;//共用的原根
  65. int inv[3][3],k1,k2,Inv,M;
  66. //inv[i][j] : MOD[i] 在 (mod MOD[j]) 下的逆元
  67. int h[3][MAXN+5],g[3][MAXN+5];
  68. //h/g[i][j] : 原函数的第 j 位在 (mod MOD[i]) 的情况下的值
  69. int revi[MAXN+5];//反转数组
  70. inline void init(){
  71. rep(i,0,2)rep(j,0,2)if(i!=j)//处理 inv 数组, 主要用到费马小定理
  72. inv[i][j]=qkpow(MOD[i],MOD[j]-2,MOD[j]);
  73. M=MOD[0]*MOD[1];
  74. k1=mulMod(MOD[1],inv[1][0],M);
  75. k2=mulMod(MOD[0],inv[0][1],M);
  76. Inv=inv[0][2]*inv[1][2]%MOD[2];
  77. }
  78. inline int crt(const int a1,const int a2,const int a3,const int mod){
  79. int A=(mulMod(a1,k1,M)+mulMod(a2,k2,M))%M;
  80. int K=(a3+MOD[2]-A%MOD[2])%MOD[2]*Inv%MOD[2];
  81. return ((M%mod)*K%mod+A)%mod;
  82. }
  83. inline void ntt(int* f,const int n,const int m,const short opt=1){
  84. /*
  85. 和普通的 ntt 没啥区别, 如果有什么问题, 可以去查查 fft 的资料
  86. 唯一有区别的地方在于取模的时候, 要根据我们目前计算的模数下的运算来取模
  87. */
  88. for(int i=0;i<n;++i)if(i<revi[i])swap(f[i],f[revi[i]]);
  89. for(int s=2;s<=n;s<<=1){
  90. int t=s>>1,u=(opt==-1)?qkpow(G,(MOD[m]-1)/s,MOD[m]):qkpow(G,MOD[m]-1-(MOD[m]-1)/s,MOD[m]);
  91. for(int i=0;i<n;i+=s){int w=1;
  92. for(int j=i;j<i+t;++j,w=w*u%MOD[m]){
  93. int x=f[j],y=w*f[j+t]%MOD[m];
  94. f[j]=(x+y)%MOD[m];
  95. f[j+t]=(x-y+MOD[m])%MOD[m];
  96. }
  97. }
  98. }
  99. if(opt==-1){
  100. int inv=qkpow(n,MOD[m]-2,MOD[m]);
  101. rep(i,0,n-1)f[i]=f[i]*inv%MOD[m];
  102. }
  103. }
  104. int n,m,p;
  105. inline void launch(){
  106. init();
  107. qread(n,m,p);
  108. rep(i,0,n){//这里我输入的最大的一个模数, 因为其已经超过 1e9 的范围, 刚刚输入时不用取模
  109. qread(h[2][i]);
  110. h[1][i]=h[2][i]%MOD[1];
  111. h[0][i]=h[2][i]%MOD[0];
  112. }
  113. rep(i,0,m){
  114. qread(g[2][i]);
  115. g[1][i]=g[2][i]%MOD[1];
  116. g[0][i]=g[2][i]%MOD[0];
  117. }
  118. for(m+=n,n=1;n<=m;n<<=1);
  119. for(int i=0;i<n;++i)revi[i]=(revi[i>>1]>>1)|((i&1)?n>>1:0);
  120. rep(i,0,2){
  121. ntt(h[i],n,i),ntt(g[i],n,i);
  122. rep(j,0,n-1)h[i][j]=h[i][j]*g[i][j]%MOD[i];
  123. ntt(h[i],n,i,-1);
  124. }
  125. for(int i=0;i<=m;++i)
  126. writc(crt(h[0][i],h[1][i],h[2][i],p),' ');
  127. //使用 crt(我国剩余定理) 来还原答案
  128. // rep(i,0,m)printf("%lld %lld %lld\n",h[0][i],h[1][i],h[2][i]);
  129. }
  130. signed main(){
  131. #ifdef FILEOI
  132. freopen("file.in","r",stdin);
  133. freopen("file.out","w",stdout);
  134. #endif
  135. launch();
  136. return 0;
  137. }

拆系数 FFT (MTT)

我太菜了,还不会...等我更新吧...

「学习笔记」FFT 之优化——NTT的更多相关文章

  1. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  2. 「学习笔记」FFT及NTT入门知识

    前言 快速傅里叶变换(\(\text{Fast Fourier Transform,FFT}\) )是一种能在\(O(n \log n)\)的时间内完成多项式乘法的算法,在\(OI\)中的应用很多,是 ...

  3. 「学习笔记」单调队列优化dp

    目录 算法 例题 最大子段和 题意 思路 代码 修剪草坪 题意 思路 代码 瑰丽华尔兹 题意 思路 代码 股票交易 题意 思路 代码 算法 使用单调队列优化dp 废话 对与一些dp的转移方程,我们可以 ...

  4. 「学习笔记」字符串基础:Hash,KMP与Trie

    「学习笔记」字符串基础:Hash,KMP与Trie 点击查看目录 目录 「学习笔记」字符串基础:Hash,KMP与Trie Hash 算法 代码 KMP 算法 前置知识:\(\text{Border} ...

  5. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  6. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  7. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

  8. 「学习笔记」斜率优化dp

    目录 算法 例题 任务安排 题意 思路 代码 [SDOI2012]任务安排 题意 思路 代码 任务安排 再改 题意 思路 练习题 [HNOI2008]玩具装箱 思路 代码 [APIO2010]特别行动 ...

  9. 「学习笔记」递推 & 递归

    引入 假设我们想计算 \(f(x) = x!\).除了简单的 for 循环,我们也可以使用递归. 递归是什么意思呢?我们可以把 \(f(x)\) 用 \(f(x - 1)\) 表示,即 \(f(x) ...

随机推荐

  1. python+pygame制作一个可自定义的动态时钟和详解

    1.效果图 2.完整代码 #第1步:导出模块 import sys, random, math, pygame from pygame.locals import * from datetime im ...

  2. 吴裕雄 python 机器学习——伯努利贝叶斯BernoulliNB模型

    import numpy as np import matplotlib.pyplot as plt from sklearn import datasets,naive_bayes from skl ...

  3. 树莓派4B踩坑指南 - (11)免费搭建网站(宝塔,花生壳)

    目录 宝塔 安装宝塔面板 登录及初始化设置 安装网站 花生壳 安装花生壳 设置花生壳 测试 问题(未解决但不影响使用) 网站统计 树莓派这么低的功耗,不用来当服务器总感觉有点浪费...完成效果:htt ...

  4. net core调用MimeKit发送QQ邮件

    一.在QQ邮箱内申请授权码,具体参考请官方文档 二.具体代码 public void TestSendMailDemo() { MimeMessage message = new MimeMessag ...

  5. Shiro&Jwt验证

    此篇基于 SpringBoot 整合 Shiro & Jwt 进行鉴权 相关代码编写与解析 首先我们创建 JwtFilter 类 继承自 BasicHttpAuthenticationFilt ...

  6. [HTTP]HTTP/1.1 协议Expect: 100-continue

    在追踪请求时发现了这么一个http头 基础知识背景:1)“Expect: 100-continue”的来龙去脉: HTTP/1.1 协议里设计 100 (Continue) HTTP 状态码的的目的是 ...

  7. 安卓基础(AndroidViewModel)

    今天学习了AndroidViewModel,但是我根据视频上讲解,根据所讲用例,在添加依赖得时候一直报错,后来我请教大佬,他告诉我说是,因为网络得问题,国外得一些依赖有可能下不下来,所以可以下载阿里云 ...

  8. 使用php-vmstat遇到的麻烦

    workerman-vmstat是一个基于workerman的扩展,用于监听服务器应用对内存.cpu消耗的友好的查看功能,具体介绍可以去git上看:    https://github.com/wal ...

  9. u盘乱码了,如何备份

    文/亡命之徒 2013年7月的最后一天,今天在公司下了些嵌入式的教程存在u盘里,准备拿回家到自己的本子上学习,不知怎的查到电脑上,显示一些文件夹,名字都是乱码,顿时心情扫地,无奈只能到互联网上寻找re ...

  10. 一张linux光盘查看是哪个版本号的方法

    在此查看版本号,方法如下:打开光盘,查找rpm包中的release,就是版本号.