[FFT/NTT/MTT]总结
最近重新学了下卷积,简单总结一下,不涉及细节内容:
1、FFT
朴素求法:$Coefficient-O(n^2)-CoefficientResult$
FFT:$Coefficient-O(nlogn)-Dot-O(n)-DotResult-O(nlogn)-CoefficientResult$
其中系数到点值的转化称为$DFT(离散傅里叶变换)$,而点值到系数的转为称为$IDFT(傅里叶逆变换)$
原本朴素的直接带入$n$个值的$DFT$和直接使用拉格朗日插值公式的$IDFT$的复杂度仍为$O(n^2)$
但$FFT$通过带入特定的值:单位根,使得两者都能迭代/分治得解决,将复杂度降到了$O(nlogn)$
优化的技巧和注意事项:
1、预处理$w[i]$
2、求出最终数组从后往前迭代省去递归常数
3、数组长度要先扩成2的倍数用于分治
模板:
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
#define pb push_back
typedef double db;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=3e6+;
struct Complex
{
db x,y;
Complex(db a=,db b=){x=a;y=b;}
Complex operator + (const Complex& rhs)
{return Complex(x+rhs.x,y+rhs.y);}
Complex operator - (const Complex& rhs)
{return Complex(x-rhs.x,y-rhs.y);}
Complex operator * (const Complex& rhs)
{return Complex(x*rhs.x-y*rhs.y,x*rhs.y+y*rhs.x);}
}a[MAXN],b[MAXN];
int n,m,lmt=,dgt,par[MAXN]; void FFT(Complex *a,int flag)
{
for(int i=;i<lmt;i++)
if(i<par[i]) swap(a[i],a[par[i]]); for(int len=;len<lmt;len<<=)
{
Complex unit(cos(M_PI/len),flag*sin(M_PI/len));
for(int st=;st<lmt;st+=(len<<))
{
Complex w(,);
for(int k=st;k<st+len;k++,w=w*unit)
{
Complex A=a[k],B=w*a[k+len];
a[k]=A+B;a[k+len]=A-B;
}
}
}
if(flag==-)
for(int i=;i<=n+m;i++)
a[i].x=floor(a[i].x/lmt+0.5);
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i<=n;i++) scanf("%lf",&a[i].x);
for(int i=;i<=m;i++) scanf("%lf",&b[i].x);
while(lmt<=n+m) lmt<<=,dgt++;
for(int i=;i<lmt;i++)
par[i]=(par[i>>]>>)|((i&)<<(dgt-)); FFT(a,);FFT(b,);
for(int i=;i<lmt;i++)
a[i]=a[i]*b[i];
FFT(a,-);
for(int i=;i<=n+m;i++)
printf("%d ",(int)a[i].x);
return ;
}
FFT
2、NTT
单位根由于涉及了复数的运算,导致对精度要求高时会出错
而$NTT$就能使得整个$FFT$都能在模意义下计算,从而满足精度要求
考虑$FFT$引入单位根$w_n^k$是为了其什么性质来分治计算:
1、$w_n^k$互不相同,保证点值表示的合法
2、$w_{t*n}^{t*k}=w_n^k$且$w_n^{k+2/n}=-w_n^k$,使得计算可分治
3、$\sum_{i=0}^{n-1} {w_n^k}^i=n*[k==0]$,保证逆矩阵构造的正确性
在模意义下引入质数$p=kn+1$,其原根$g$满足$g_t(t\in [0,p-1])$互不相同
这样令$p$的$k$次单位根为$g^{\frac{p-1}{k}}$,易证上述$w_n^k$的性质其在模意义下均满足
接下来考虑该怎样选择质数$p$
为了能够分治时允许$k$每次乘2,$p-1$的质因数分解中要有很多的2
令$p=r*2^k+1$,其能处理的数据规模为$[0,2^k]$,常用质数有:传送门
这样,我们就在模意义下利用原根的性质找到了可做$FFT$的“单位根”
由于没有了复数运算,$NTT$比$FFT$的常数也小了很多,一般是更好的选择
模板:
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
#define pb push_back
typedef double db;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=4e6+,MOD=;
ll n,m,a[MAXN],b[MAXN],dgt,lmt=,par[MAXN]; ll quick_pow(ll a,ll b)
{
ll ret=;
for(;b;b>>=,a=a*a%MOD)
if(b&) ret=ret*a%MOD;
return ret;
}
void FFT(ll *a,int flag)
{
for(int i=;i<lmt;i++)
if(i<par[i]) swap(a[i],a[par[i]]);
for(int len=;len<lmt;len<<=)
{
ll unit=quick_pow(,(MOD-)/(len<<));
if(flag==-) unit=quick_pow(unit,MOD-);
for(int st=;st<lmt;st+=(len<<))
{
ll w=;
for(int k=st;k<st+len;k++,w=w*unit%MOD)
{
ll A=a[k],B=w*a[k+len]%MOD;
a[k]=(A+B)%MOD;a[k+len]=(A-B+MOD)%MOD;
}
}
}
} int main()
{
scanf("%lld%lld",&n,&m);
for(int i=;i<=n;i++) scanf("%lld",&a[i]);
for(int i=;i<=m;i++) scanf("%lld",&b[i]);
while(lmt<=n+m) lmt<<=,dgt++;
for(int i=;i<lmt;i++)
par[i]=(par[i>>]>>)|((i&)<<(dgt-)); FFT(a,);FFT(b,);
for(int i=;i<lmt;i++)
(a[i]*=b[i])%=MOD;
FFT(a,-);
ll inv=quick_pow(lmt,MOD-);
for(int i=;i<=n+m;i++)
printf("%lld ",a[i]*inv%MOD);
return ;
}
NTT
3、MTT
如果答案需要取模且模数非质数该如何处理呢?
常见背景为:多项式长度$1e5$,模数$1e9$非质数,此时$FFT$爆$longlong$,没法用$NTT$
(1)三模数$NTT$
根据上方的数据限制,可发现最终答案最多为$1e23$
这样就能用多个乘积大于$1e23$的模数分别做$NTT$最后再用$CRT$合并答案即可
一般常用:469762049,998244353,1004535809
可如果直接用$CRT$合并会发现模数爆$longlong$还是不好处理
此时就可以先合并前两个式子,得到
$res=k(mod(p_1*p_2)),res=a_3(mod(p_3))$
这样设$res=p_1*p_2*c+k$再带入二式就能得到$c=(a_3-k)*(p_1*p_2)^{-1}(mod(p_3))$
这样类似$exCRT$的分步处理就避开了对$p_1*p_2*p_3$的取模
但这样要进行9次$DFT/IDFT$,常数巨大无比
模板:
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
#define pb push_back
typedef double db;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=4e5+;
ll p[]={,,};
int n,m,MOD,F[MAXN],G[MAXN],dgt,lmt=;
ll a[][MAXN],b[MAXN],res[MAXN],par[MAXN]; ll quickpow(ll a,ll b,ll MOD)
{
a%=MOD;ll ret=;
for(;b;b>>=,a=a*a%MOD)
if(b&) ret=ret*a%MOD;
return ret;
}
ll mul(ll a,ll b,ll MOD)
{
a=(a%MOD+MOD)%MOD;
b=(b%MOD+MOD)%MOD;ll ret=;
for(;b;b>>=,a=(a+a)%MOD)
if(b&) (ret+=a)%=MOD;
return ret;
}
ll inv(ll a,ll MOD)
{return quickpow(a,MOD-,MOD);}
void FFT(ll *a,int flag,ll MOD)
{
for(int i=;i<lmt;i++)
if(i<par[i]) swap(a[i],a[par[i]]);
for(int len=;len<lmt;len<<=)
{
ll unit=quickpow(,(MOD-)/(len<<),MOD);
if(flag==-) unit=inv(unit,MOD);
for(int st=;st<lmt;st+=(len<<))
{
ll w=;
for(int k=st;k<st+len;k++,w=w*unit%MOD)
{
ll A=a[k],B=w*a[k+len]%MOD;
a[k]=(A+B)%MOD;a[k+len]=(A-B+MOD)%MOD;
}
}
}
if(flag==-)
{
ll INV=inv(lmt,MOD);
for(int i=;i<lmt;i++)
a[i]=a[i]*INV%MOD;
}
}
void solve(ll *a,ll *b,ll MOD)
{
for(int i=;i<=n;i++) a[i]=F[i];
for(int i=;i<=m;i++) b[i]=G[i];
for(int i=m+;i<lmt;i++) b[i]=;
FFT(a,,MOD);FFT(b,,MOD);
for(int i=;i<lmt;i++) a[i]=a[i]*b[i]%MOD;
FFT(a,-,MOD);
} int main()
{
scanf("%d%d%d",&n,&m,&MOD);
for(int i=;i<=n;i++) scanf("%d",&F[i]);
for(int i=;i<=m;i++) scanf("%d",&G[i]);
while(lmt<=n+m) lmt<<=,dgt++;
for(int i=;i<lmt;i++) par[i]=(par[i>>]>>)|((i&)<<(dgt-)); for(int i=;i<;i++) solve(a[i],b,p[i]);
for(int i=;i<=n+m;i++)
{
ll M=p[]*p[];
ll A=(mul(a[][i]*p[],inv(p[],p[]),M)+
mul(a[][i]*p[],inv(p[],p[]),M))%M;
ll K=mul(a[][i]-A,inv(M,p[]),p[]);
res[i]=(mul(K,M,MOD)+A%MOD)%MOD;
}
for(int i=;i<=n+m;i++)
printf("%lld ",res[i]);
return ;
}
三模数NTT
(2)拆系数$FFT$
不能用$FFT$仅仅因为最后答案会爆$longlong$,那么可以将原数拆分后分别计算
$A_i=a_i* \sqrt{P}+b_i,B_i=c_i* \sqrt{P}+d_i$
此时$A*B=P*(a*c)+\sqrt{P}*(a*d+b*c)+(b*d)$,每部分最大值为$1e14$,分别$DFT/IDFT$
这样要做7次$DFT/IDFT$,效率未显著提升
$myy$在论文里提到过对FFT的优化:
设$P_j=A_j+i*B_j,Q_j=A_j-i*B_j$,使得$DFT$前虚部不再为空
可推出$DFT$后的$DP,DQ$数组如下结论:
$DP_k=\sum_{j=0}^{lmt-1} (A_j+i*B_j)*w_{lmt}^{j*k},DQ_k=conj(DP_{lmt-k})$
这样就能用1次对$P$的$DFT$算出$P,Q,A,B$的$DFT$,从而将上面的4次$DFT$化为2次
由于$IDFT$就能看成$DFT$的逆过程
因此可以合并算出$IDFT(DFT[a]*DFT[c]+i*DFT[b]*DFT[d])$,从而将$IDFT$也化为2次
这样的常数经测试是三模数$NTT$的1/7左右
模板:
#include <bits/stdc++.h> using namespace std;
#define X first
#define Y second
#define pb push_back
typedef double db;
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e6+;
struct Complex
{
db x,y;
Complex(db a=,db b=){x=a;y=b;}
Complex operator +(const Complex& rhs)
{return Complex(x+rhs.x,y+rhs.y);}
Complex operator -(const Complex& rhs)
{return Complex(x-rhs.x,y-rhs.y);}
Complex operator *(const Complex& rhs)
{return Complex(x*rhs.x-y*rhs.y,x*rhs.y+y*rhs.x);}
}a[MAXN],b[MAXN],w[MAXN],t1[MAXN],t2[MAXN],t3[MAXN];
int n,m,MOD,lmt=,dgt,par[MAXN];ll x,res[MAXN]; void FFT(Complex *a,int flag)
{
for(int i=;i<lmt;i++)
if(i<par[i]) swap(a[i],a[par[i]]);
for(int len=;len<lmt;len<<=)
for(int st=;st<lmt;st+=(len<<))
{
int cur=;
for(int k=st;k<st+len;k++)
{
Complex A=a[k],B=w[cur]*a[k+len];
a[k]=A+B;a[k+len]=A-B;
//预处理的写法
cur=(cur+flag*lmt/(len<<)+lmt)&(lmt-);
}
}
if(flag==-)
for(int i=;i<lmt;i++)
a[i].x=floor(a[i].x/lmt+0.5);
}
void solve()
{
FFT(a,);FFT(b,);
for(int i=;i<lmt;i++)
{
Complex d1,d2,d3,d4;
int j=(lmt-i)&(lmt-);
d1=(a[i]+Complex(a[j].x,-a[j].y))*Complex(0.5,);
d2=(a[i]-Complex(a[j].x,-a[j].y))*Complex(,-0.5);
d3=(b[i]+Complex(b[j].x,-b[j].y))*Complex(0.5,);
d4=(b[i]-Complex(b[j].x,-b[j].y))*Complex(,-0.5);
//必须先用临时变量存,因为后面还要用
t1[i]=d1*d3;t2[i]=d1*d4+d2*d3;t3[i]=d2*d4;
}
for(int i=;i<lmt;i++)
//充分利用虚部空间(可看成逆过程)
b[i]=t2[i],a[i]=t1[i]+t3[i]*Complex(,);
FFT(a,-);FFT(b,-);
for(int i=;i<lmt;i++)
{
ll k1=(ll)a[i].x%MOD,k2=(ll)b[i].x%MOD;
ll k3=(ll)floor(a[i].y/lmt+0.5)%MOD;
res[i]=((k3<<)%MOD+(k2<<)%MOD+k1)%MOD;
}
} int main()
{
scanf("%d%d%d",&n,&m,&MOD);
for(int i=;i<=n;i++)
scanf("%lld",&x),a[i]=Complex(x&,x>>);
for(int i=;i<=m;i++)
scanf("%lld",&x),b[i]=Complex(x&,x>>);
while(lmt<=n+m) lmt<<=,dgt++;
for(int i=;i<lmt;i++)
par[i]=(par[i>>]>>)|((i&)<<(dgt-));
for(int i=;i<lmt;i++)
w[i]=Complex(cos(*M_PI*i/lmt),sin(*M_PI*i/lmt)); solve();
for(int i=;i<=n+m;i++)
printf("%lld ",res[i]);
return ;
}
拆系数FFT
[FFT/NTT/MTT]总结的更多相关文章
- FFT/NTT/MTT学习笔记
FFT/NTT/MTT Tags:数学 作业部落 评论地址 前言 这是网上的优秀博客 并不建议初学者看我的博客,因为我也不是很了解FFT的具体原理 一.概述 两个多项式相乘,不用\(N^2\),通过\ ...
- FFT&NTT总结
FFT&NTT总结 一些概念 \(DFT:\)离散傅里叶变换\(\rightarrow O(n^2)\)计算多项式卷积 \(FFT:\)快速傅里叶变换\(\rightarrow O(nlogn ...
- Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门
进阶篇戳这里. 目录 何为「多项式」 基本概念 系数表示法 & 点值表示法 傅里叶(Fourier)变换 概述 前置知识 - 复数 单位根 快速傅里叶正变换(FFT) 快速傅里叶逆变换(I ...
- FFT \ NTT总结(多项式的构造方法)
前言.FFT NTT 算法 网上有很多,这里不再赘述. 模板见我的代码库: FFT:戳我 NTT:戳我 正经向:FFT题目解题思路 \(FFT\)这个玩意不可能直接裸考的..... 其实一般\(FF ...
- [学习笔记&教程] 信号, 集合, 多项式, 以及各种卷积性变换 (FFT,NTT,FWT,FMT)
目录 信号, 集合, 多项式, 以及卷积性变换 卷积 卷积性变换 傅里叶变换与信号 引入: 信号分析 变换的基础: 复数 傅里叶变换 离散傅里叶变换 FFT 与多项式 \(n\) 次单位复根 消去引理 ...
- 快速构造FFT/NTT
@(学习笔记)[FFT, NTT] 问题概述 给出两个次数为\(n\)的多项式\(A\)和\(B\), 要求在\(O(n \log n)\)内求出它们的卷积, 即对于结果\(C\)的每一项, 都有\[ ...
- FFT/NTT模板 既 HDU1402 A * B Problem Plus
@(学习笔记)[FFT, NTT] Problem Description Calculate A * B. Input Each line will contain two integers A a ...
- FFT/NTT基础题总结
在学各种数各种反演之前把以前做的$FFT$/$NTT$的题整理一遍 还请数论$dalao$口下留情 T1快速傅立叶之二 题目中要求求出 $c_k=\sum\limits_{i=k}^{n-1}a_i* ...
- $FFT/NTT/FWT$题单&简要题解
打算写一个多项式总结. 虽然自己菜得太真实了. 好像四级标题太小了,下次写博客的时候再考虑一下. 模板 \(FFT\)模板 #include <iostream> #include < ...
随机推荐
- 转载:《理解RESTful架构》 阮一峰
原文:http://www.ruanyifeng.com/blog/2011/09/restful.html 越来越多的人开始意识到,网站即软件,而且是一种新型的软件. 这种"互联网软件&q ...
- spring事物深入了解
1.问题 1.以前对事物的了解只是停留在声明式事物,配置xml,或使用注解,事物的传播行为也只用过REQUIRED和SUPPORTS,可以说对事物的了解很模糊. 2.直到在开发中遇到问题.. 问题的描 ...
- SpringMVC(1):Web MVC简介
原文出处: 张开涛 Web MVC简介 1.1.Web开发中的请求-响应模型: 在Web世界里,具体步骤如下: 1. Web浏览器(如IE)发起请求,如访问 2. Web服务器(如Tomcat)接 ...
- pytest四:fixture_yield 实现 teardown
既然有 setup 那就有 teardown,fixture 里面的 teardown 用 yield 来唤醒 teardown的执行 在所有用例执行完后执行:yield import pytest ...
- 绝对定位后,position:absolute;不能使用margin: 0 auto;实现居中;
声明: web小白的笔记,欢迎大神指点!联系QQ:1522025433. 我们都知道margin: 0 auto:可也实现块状元素的水平居中:但是对于绝对顶为的元素就会失效: 请看实例: <!d ...
- 使用siege执行压力测试
没有安装siege? 可参考我的另一篇博客 使用siege执行压力测试笔记 场景分析 使用siege对https://www.baidu.com/进行加压. 要求 模拟20个用户同时访问 一共跑3个循 ...
- python 全栈开发,Day121(DButils,websocket)
昨日内容回顾 1.Flask路由 1.endpoint="user" # 反向url地址 2.url_address = url_for("user") 3.m ...
- 2017-2018-2 20155225《网络对抗技术》实验八 Web基础
2017-2018-2 20155225<网络对抗技术>实验八 Web基础 1.Web前端HTML 输入命令apachectl start打开apahce,并使用netstat -aptn ...
- HDFS上创建文件、写入内容
1.创建文件 hdfs dfs -touchz /aaa/aa.txt 2.写入内容 echo "<Text to append>" | hdfs dfs -appen ...
- 开始写博客,学习Linq(3)
为什么需要Linq?(摘自原文) 读者会发现LINQ着眼于解决编程语言和数据库之间广发存在的不统一问题. 1.常见的问题,使用.NET Framework Class Library(FCL)提供了A ...