NTT&FFT(以及扩展)

预先知识:无

我觉得我们可以从NTT/FFT讲起?

两个其实本质相同,都是求 多项式乘积 的算法

FFT

\((x,y)\)指复数

基本运算\((x,y)+(a,b)=(x+a,y+b),(x,y)\cdot (a,b)=(ax-by,ay+bx)\)

首先我们构造单位根\(\omega_n\)=\((cos(2\pi/n),sin(2\pi/n))\)

而\((\omega _n)^i=(cos(2\pi/n\cdot i),sin(2\pi/n\cdot i))\)

伟大的数学家们告诉我们\((\omega_n)^n=1\)

也就是说\(\omega_n\)实际上是一个\(n\)元循环的根

\[\
\]

NTT

首先我们对于一个模数\(P\)(通常是质数),构造单位根\(g\),使得满足\(\{k\in N^+|g^k \equiv 1 (\mod P)\}\)的最小的\(k\)是\(P-1\)

伟大的数学家们告诉我们,这个\(g\)是\(P\)的一个原根(?)

也就是说,\(g^k\)在模\(P\)意义下呈现\(P-1\) 次一循环

Tips:构造模意义下的单位根就以为这我们求出的系数都是对于\(P\)取模过的

通常我们\(P\)取\(998244353\),\(2^{23}|(P-1)\),它的一个原根是3

\[\
\]

多项式和点值式

一个多项式\(f(x)=\sum_0^{n-1} a_i x^i\)

可以表示成n个点\((x_i,y_i)\),其中\(x_i \ne x_j(i\ne j)\)

也就是说我们可以通过消元和暴力计算完成\(O(n^2)/O(n^3)\)的点值转化

伟大的数学家们又告诉我们,这样的点值式一定可以完成唯一的反解

点值式有几个简单的性质

\(\{(x_i,y_i)\} + \{(x_i,z_i)\}=\{(x_i,y_i + z_i)\}\)

\(\{(x_i,y_i)\} \cdot \{(x_i,z_i)\}=\{(x_i,y_i \cdot z_i)\}\)

这个东西想必不需要我多说了

\[\
\]

好了我们开始学习FFT/NTT(?)

对于单位根,无论是什么单位根,我们可以利用分治的方式快速完成点值式与多项式之间的转化,利用这一点,我们可以得到\(FFT/NTT\)的主要流程

多项式\(\Longrightarrow\)点值式\(\Longrightarrow\)点值式相乘\(\Longrightarrow\)多项式

可以看到,这个东西是\(nlogn\)

接下来我们考虑如何将多项式转化为点值式

我们构造特殊的\(x_i=\omega_n^i(g^i)\)(哪一个都可以)

单位根有一个明显的性质帮助我们解题\((\omega_n)^i=(\omega_{n/2})^{i/2}\)(折半引理)

成立条件\((2|i,2|n)\)

对于\(g\)我们可以认为\(\omega _n\)=\(g^{(P-1)/n}\)

要求 \(f(x_1)=\sum a_i\cdot (\omega_n)^i\)

我们可以先求出

\(g(x_1)=a_0\omega_{n/2}^0+a_2\omega_{n/2}^2..\)

\(h(x_1)= a_1\omega_{n/2}^0+a_3\omega_{n/2}^2..\)

其实就是分治子问题

然后我们通过合并得到\(f(x)\)

\(g(x_1) \Longleftrightarrow a_0\omega_n^0+a_2\omega_n^2..\)

\(h(x_1) \Longleftrightarrow a_1\omega_n^0+a_3\omega_n^2..\)

我们要求的是 \(f(x_1)=a_0\cdot\omega_n^0+a_2\omega_n^2\cdots+a_1\omega_n^1+a_3\omega_n^3\cdots\)

\(f(x_1)=g(x_1)+x^1h(x_1)\)

啊!

还有\(f(x_i)=g(x_i)+x^i h(x_i)\)

这个过程看似顺畅,实际上我们必须考虑最重要的问题\(\omega_n^i\rightarrow \omega _{n/2}^{i/2}\)真的能求吗?

我们要保证每一次分治时\(n\)都是\(2\)的倍数,对于所有数据适用,所以\(n\)必须转化为\(2^k\)

int N=1;
while(N<=n+m) N<<=1;

这样我们就能好好写分治了

tips:回到之前\(g_n\)的求法,\(n|(P-1)\)是一个大前提,所以我们找到的所有\(P-1\)都是\(2^k\)倍数

\[\
\]

关于它的逆运算:伟大的数学家们告诉我们直接将原根取成倒数做一次变换,最后全部除以\(n\)即可

(复数的倒数是共轭复数\((a,b)*(c,d)=(1,0)\),原根的倒数是模逆元)

但是由于过(我)于(太)冗(菜)长(了),不予展开

另有 \((\omega_n)^{-i}=(cos(2\pi/n\cdot i),-sin(2\pi/n\cdot i))\) (诱导公式?)

Tips: 由于整个过程的单位根对于\(n\)取模,所以求得的其实是\(f(x^{i\mod n})\)

代码实现

模板题传送门

然后我们得到一份优美的代码(FFT)

(Complex是C++库自带的复数,他们的运算我们暂时不管,M_PI是C++自带\(\pi\)常量)

void FFT(int n,Complex *a,int f) {
if(n==1) return;
Complex tmp[N];
int m=n/2;
rep(i,0,m-1) tmp[i]=a[i<<1],tmp[i+m]=a[i<<1|1]; // 按照奇偶分类
memcpy(a,tmp,sizeof(T)*n);
DFT(m,a,f),DFT(m,a+m,f); // 分两半,算g(x),h(x)
Complex w(cos(2*M_PI/n),f*sin(2*M_PI/n)),e(1,0); // w=x^1,e=x^i
rep(i,0,m-1) {
tmp[i]=a[i]+e*a[i+m]; // f(x_i)=g(x_i)-e*h(x_i)
e=e*w;
}
rep(i,m,n-1) {
tmp[i]=a[i-m]+e*a[i];
e=e*w;
}
memcpy(a,tmp,sizeof(T)*n);
}

由于\((\omega_n)^{\frac{n}{2}}=-1\)(额自己理解一下,或者带一下\(\omega\)的式子)

所以还可以简化为

	Complex w(cos(2*M_PI/n),f*sin(2*M_PI/n)),e(1,0);
rep(i,0,m-1) {
tmp[i]=a[i]+e*a[i+m];
tmp[i+m]=a[i]-e*a[i+m];
e=e*w;
}

由于用了double,最后输出要取整

但是这份代码跑得依然巨慢,简直连1e6都做不了

我们加一点优化,取代递归的分治过程

可以看到,分治时我们按照\(i \mod 2\)分成两组,然后继续分

这个过程中,实际上我们就是将\(i\)的二进制位前后翻转

所以我们可以暴力处理出\(i\)分治底层的位置

rep(i,0,n-1) {
int x=i,s=0;
for(int j=1;(j<<c)<=n;++j) {
s=(s<<1)|(x&1);
x>>=1;
} // s就是最终位置
}

当然也是有\(O(n)\)处理方法的

int N=1,c=-1;
while(N<=n+m) N<<=1,c++;
rep(i,1,N-1) rev[i]=(rev[i>>1]>>1)|((i&1)<<c);

(建议自己模拟一下)

接下来我们枚举分治长度\(i\),枚举合并区间的最短点\(l\),枚举合并的位置\(j\)

合并方法和上文一样,但是由于这里没有另外开一个\(temp\)数组,所以合并时要注意一下顺序

for(int i=2;i<=n;++i) {
int m=i/2;
Complex w(cos(2*M_PI/n),f*sin(2*M_PI/n));
for(int l=0;l<n;l+=i) {
Complex e(1,0);
for(int j=l;j<l+i;++j,e=e*w) {
Complex t=a[j+m]*e; // a'[j]=a[j]+e*a[j+m]
// a'[j+i]=a[j]-e*a[j+m]
a[j+m]=a[j]-t;
a[j]=a[j]+t;
}
}
}

事实上我们还有更快的写法,就是将\((\omega_n)^i\)预处理出来,就避免了多次计算,这个代码留给读者自己写

当然如果自己写复数会更快

// Complex
struct Cp{
double x,y;
Cp(){}
Cp(double _x,double _y){ x=_x,y=_y; }
Cp operator + (const Cp t) { return Cp(x+t.x,y+t.y); }
Cp operator - (const Cp t) { return Cp(x-t.x,y-t.y); }
Cp operator * (const Cp t) { return Cp(x*t.x-y*t.y,x*t.y+y*t.x); }
};

这个运算可以背一下...

\[\
\]

顺便提供一份我平时的NTT模板(希望没有敲错)

const int N=1<<18|5,P=998244353;

ll a[N],b[N],w[N];
int rev[N];
void NTT(int n,ll *a,int f){
rep(i,0,n-1)if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<n;i<<=1) {
int len=n/i/2;
for(int l=0;l<n;l+=i*2) {
for(int j=l;j<l+i;++j) {
ll t=a[j+i]*w[len*(j-l)]%P;
a[j+i]=(a[j]-t)%P;
a[j]=(a[j]+t)%P;
}
}
}
if(f==-1) {
ll base=qpow(n,P-2);
rep(i,0,n-1) a[i]=a[i]*base%P;
}
}
int main(){
int R=1,c=-1;
while(R<=n)R<<=1,c++;
rep(i,1,R) rev[i]=(rev[i>>1]>>1)|((i&1)<<c);
w[0]=1,w[1]=qpow(3,(P-1)/R);
rep(i,1,R-1)w[i]=w[i-1]*w[1]%P;
NTT(R,a,1),NTT(R,b,1);
w[0]=1,w[1]=qpow((P+1)/3,(P-1)/R);
rep(i,1,R-1)w[i]=w[i-1]*w[1]%P;
rep(i,0,R-1)a[i]=a[i]*b[i]%P;
NTT(R,a,-1);
}

练习建议:

1.高精度乘法

2.简单应用:HDU-4609 题解

3.卷积构造模板: BZOJ-3527 题解

4.拓展卷积构造:HDU-5885 题解

5.构造卷积的应用:HDU-6061 题解

然后去学下面的拓展1,2,3

6.\(CDQ\)分治+\(FFT\):HDU-5730 题解

7.\(CDQ\)+NTT/降次前缀和优化\(dp\):HDU-5332 题解

8.容斥+\(MTT\):HDU-6088 题解

9.图上\(dp\):

联通图个数:BZOJ-3456 题解

带环联通图个数:HDU-5552 题解

森林数量和带限制森林数量:HDU - 5279 题解

10.点分治+FFT:CodeChef-PRIMEDST 题解

\[\
\]

\[\
\]

\[\
\]

拓展

1.分治+NTT

常用于处理n元计数背包的快速合并

我们可以用NTT\(nlogn\)合并两个大小为\(n\)的背包

分治时,每次合并两个分治子问题,总共的时间就是\(\sum size\log n\)

每个背包的\(size\)会被计算\(\log n\)次,所以总共复杂度是\(n \log ^2 n\)

\[\
\]

2.CDQ+NTT

模板题传送门

对于形如\(dp[i]=\sum_0^{i-1}dp[j]g[i-j]\)的\(dp\)转移(就是dp转移与差值有关)

我们用NTT/FFT对于每个\((l,mid)\)向\((mid+1,r)\)的转移,总复杂度为\(n\log^2 n\)

算法流程

void Solve(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
Solve(l,mid);
(l,mid)->(mid+1,r);
Solve(mid+1,r);
}

\[\
\]

3.MTT(任意模数NTT)

模板题传送门

就是模数比较奇怪,不能直接算,我们要先算出准确值再取模,但是double算绝对爆炸...

通常有一种比较假但是很好理解:取几个互质的模数分别做一次,然后中国剩余定理合并(贼慢)

正解:拆系数FFT

将系数\(a_i\)分成\(A_i\cdot S+C_i\)

\(b_i\):\(B_i\cdot S+D_i\)

(其中\(S\ge \sqrt{Mod}\)是一个常数,\(0 \leq A_i,B_i,C_i,D_i<S\))

转化后系数值域变小,double精度吃得消

最后的答案转化为\(A_iB_jS^2+(C_iB_j+A_iD_j)S+C_iD_j\)

如果直接求解,可以看出要求四次乘积,\(FFT12\)次,不可接受

利用虚数的一些性质,有些东西我们可以一起算

构造

\(f(x)=\sum (A_i,C_i)x^i\)

\(g(x)=\sum(B_i,D_i)x^i\)

\(f(x)g(x)=\sum \sum (A_iB_j-C_iD_j, A_iD_j+C_iB_j)x^{i+j}\)

(其实已经求出一半了)

\(h(x)=\sum B_ix^i\)

\(f(x)h(x)=\sum \sum (A_iB_j,C_iB_j)x^{i+j}\)

取一部分即可

最终一共有5次FFT

Tips:负数取整一定要注意,因为是向0取整,而不是向下取整

\[\
\]

4.\(n\)元点值式

(如果是NTT,必须满足\(n|(P-1)\))

设\(\omega _n\)=t

看到我们要求的\(f(x^k)=\sum a_i\cdot t^{i k}\)

\(i\cdot k=\cfrac{i^2+k^2-(i-k)^2}{2}\)

我们可以对于每一个\(a_i\)计算其对于每个\(f(x^k)\)的贡献(Bluestein’s Algorithm)

具体过程:

构造\(g(x)=\sum a_it^{i^2}x^i,h(x)=t^{-i^2}x^i\)

将两个式子相乘,得到了\(\cfrac{f(x^{2k})}{t^{k^2}}\)

最后再乱处理一下即可

n元点值式有啥用?

上文我们提到,FFT/NTT得到的结果是\(f(x^{i\mod n})\)

也就是说系数同样存在循环关系,我们可以利用\(n\)元卷积做到指定大小的循环卷积,可以处理一些特定问题

\[\
\]

\[\
\]

更多应用和优化参见毛啸2016论文

(如:两次FFT做卷积,4次FFT做MTT。。。)

NTT&FFT(快速?变换)的更多相关文章

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

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

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

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

  3. 浅谈FFT(快速博立叶变换)&学习笔记

    0XFF---FFT是啥? FFT是一种DFT的高效算法,称为快速傅立叶变换(fast Fourier transform),它根据离散傅氏变换的奇.偶.虚.实等 特性,对离散傅立叶变换的算法进行改进 ...

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

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

  5. BZOJ 2179: FFT快速傅立叶

    2179: FFT快速傅立叶 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 2923  Solved: 1498[Submit][Status][Di ...

  6. 【bzoj2179】FFT快速傅立叶 FFT模板

    2016-06-01  09:34:54 很久很久很久以前写的了... 今天又比较了一下效率,貌似手写复数要快很多. 贴一下模板: #include<iostream> #include& ...

  7. 计算机中的颜色XIV——快速变换颜色的V分量

    基本知识回顾: 计算机中的颜色Color,用RGB模式存储(用R.G.B三个分量表示颜色,每个分量的范围是0—255). 而计算机中的颜色除了用RGB模式表示以外,常见的还有HSV模式(或者是HSB. ...

  8. FFT 快速傅里叶变换 学习笔记

    FFT 快速傅里叶变换 前言 lmc,ikka,attack等众多大佬都没教会的我终于要自己填坑了. 又是机房里最后一个学fft的人 早背过圆周率50位填坑了 用处 多项式乘法 卷积 \(g(x)=a ...

  9. 【BZOJ 2179】 2179: FFT快速傅立叶 (FFT)

    2179: FFT快速傅立叶 Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 3308  Solved: 1720 Description 给出两个n位 ...

随机推荐

  1. 也作一下装配脑袋的Expression习题【转】

    一.习题 http://www.cnblogs.com/Ninputer/archive/2009/08/28/expression_tree1.html 二.参考 http://msdn.micro ...

  2. 个性化你的 Git Log 的输出格式

    个性化你的 Git Log 的输出格式

  3. C# 方法默认访问级别 : private C# 类默认访问级别 : internal

    C# 方法默认访问级别 : private C# 类默认访问级别 : internalpublic:访问不受限制.protected:访问仅限于包含类或从包含类派生的类型.Internal:访问仅限于 ...

  4. QT+OpenGL(04)—freetype库的编译

    1.freetype库的下载 https://www.freetype.org/download.html freetype-2.10.0.tar.bz2 2.解压 3.进入  freetype-2. ...

  5. node整个环境的启动

    1.启动mongodb服务 在 下载 / mongodb-osx- / 文件夹 新建终端窗口 ,输入命令,启动mongodb服务: mongod --dbpath data --logpath log ...

  6. 大学外语四六级英语词汇CET

    anticipation n. 预期,期望 appreciation n. 感谢,感激 array n. 陈列,一系列 assurance n. 保证 emergency n. 紧急情况 encour ...

  7. windows elasticsearch使用ik分词器插件后启动报错java.security.AccessControlException: access denied ("java.io.FilePermission" "D:...........\plugins\ik-analyzer\config\IKAnalyzer.cfg.xml" "read")

    删除es安装文件夹中空格,遂解决......(哭

  8. pip安装模块使用国内镜像源加速安装

    今天在安装Python模块matplotlib的时候,一直安装不成功,老是提示“socket.timeout: The read operation timed out”或者“Read timed o ...

  9. 每次都能让人头大的 Shader -- 从整合说起

    之前也说过引擎能不能提供一个一般化的开发环境给使用者, 这样使用者只需要指定他要的开发环境, 就能用它最熟悉的方式去写Shader了. 从提供者的角度来看, 因为有太多的应用场景无法确定, 所以提供无 ...

  10. 201871010117--石欣钰--《面向对象程序设计(java)》第十六周学习总结

    博文正文开头格式:(2分) 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh 这个作业的要求在哪里 https://www.cnblogs.com ...