什么是FFT#

既然打开了这篇博客,大家肯定都已经对FFT(Fast Fourier Transformation)有一点点了解了吧

FFT即为快速傅里叶变换,可以快速求卷积(当然不止这一些应用,但是我不会)

系数表示法与点值表示法#

我们通常表示一个\(n-1\)次多项式是利用系数表示法like this:\(f(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}\)

点值表示法即为将多项式用坐标系上的若干个点表示

我们对这个多项式代入不同的值{\(x_1,x_2,...,x_n\)}

我们就可以得到\(n\)个点\((x_1,f(x_1)),(x_2,f(x_2)),...,(x_n,f(x_n))\)

实际上只要保证代入的\(n\)个数互不相同,那么这\(n\)个点就对应了唯一的\(f(x)\)(类似\(n\)元一次方程组?)

点值表示法它的优势在哪呢?

我们如果想要求出两个多项式相乘,系数表示法就很麻烦,而点值表示法却只需要将相同的\(x\)对应的点值相乘就行了

DFT与IDFT#

DFT##

我们若将一个多项式强行转为点值表示法则时间复杂度为\(O(n^2)\)

自然有人表示:太慢啦,就不能快点吗!

于是我们开始思考,如果我们对于\((x_1,f(x_1)),(x_2,f(x_2)),...,(x_n,f(x_n))\)取一系列特殊的有关系的值,是不是能加速呢?

当然可以,要不然就不会有这篇博客了

在DFT中,我们可以将复平面中的单位园n等分,然后每一等分的顶点\(\omega _n^i\)作为\(x_i\),\(\omega _n^i\)就是单位根

(如图为\(8\)等分)



其中\(\omega _n^i=\cos (\frac{{2\pi i}}{n}) + i\sin (\frac{{2\pi i}}{n})\)

当然DFT只是让\((x_1,f(x_1)),(x_2,f(x_2)),...,(x_n,f(x_n))\)变得特殊了一些,并没有优化复杂度的效果(要不然哪来的FFT)

IDFT##

IDFT(Inverse Discrete Fourier Transform)顾名思义就是DFT的逆变换(当然我不会,这个东西,可以会,但没必要)

关于单位根#

首先,显然\(\omega _{2n}^{2i}=\omega _n^i\)(结合图像可得)\((n=2^k)\)

\(\omega _n^{i+\frac{n}{2}}=-\omega _n^i\)为什么呢,因为这两个单位根在坐标中终点关于原点对称

最后一个没有多大用的东西,但是后面会用到\(\omega _n^0=\omega _n^n=1\),这个显然吧,因为这个顶点是重合的,且虚部为0

FFT#

递归解法##

FFT(Fast Fourier Transformation)快速傅里叶变换,有了上面这些东西,我们就可以考虑对原多项式进行变形

\(f(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}\)

\(=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+x(a_1+a_3x^2+...+a_{n-1}x^{n-2})\)

是不是发现这两个东西很像啊

我们设\(f_1(x)=(a_0+a_2x+...+a_{n-2}x^{\frac{n}{2}-1}),f_2(x)=(a_1+a_3x+a_{n-1}x^{\frac{n}{2}-1})\)

所以\(f(x)=f_1(x^2)+xf_2(x^2)\)

我们发现,\(f_1(x),f_2(x)\)和\(f(x)\)的形式一样诶!

也就是说我们现在这个FFT已经可以用递归解决了!

然而,递归自带sb大常数,时间上并不能通过

于是,厉害的人们又发现了新的操作,蝴蝶变换

蝴蝶变换##

首先我们令\(i<\frac{n}{2}\)

\(f(\omega _n^i)=f_1({(\omega _n^i)}^2)+\omega _n^if_2({(\omega _n^i)}^2)\)

\(=f_1(\omega _n^{2i})+\omega _n^if_2(\omega _n^{2i})\)

\(=f_1(\omega _{\frac{n}{2}}^i)+\omega _n^if_2(\omega _{\frac{n}{2}}^i)\)

\(f(\omega _n^{i+\frac{n}{2}})=f_1({(\omega _n^i)}^2\omega _n^n)+\omega _n^{i+\frac{n}{2}}f_2({(\omega _n^i)}^2\omega _n^n)\)

\(=f_1(\omega _n^{2i})-\omega _n^if_2(\omega _n^{2i})\)

\(=f_1(\omega _{\frac{n}{2}}^i)-\omega _n^if_2(\omega _{\frac{n}{2}}^i)\)

是不是很神奇,是不是!

就差了一个符号,是不是很蝴蝶啊(亲爱的,你慢慢飞,小心...)

有了蝴蝶变换,我们发现我们只要知道\(f_1(\omega _{\frac{n}{2}}^i),f_2(\omega _{\frac{n}{2}}^i)\)就可以求出\(n\)等分下两个不同单位根带入后的点值了,是不是很厉害!

rev数组##

但是我们又发现了一个新的问题,每次我们合并的时候,都是把要求的序列分为奇数位和偶数位做

可是我们原数组并不符合这个条件,直接做的话肯定死

我们可以研究一下每个数字和它的二进制表示

以8个数为例:



但是我们合并的顺序应该长成这样:

(读者可以自己模拟一下,段长每次为\(2^k\),然后合并)

我们发现,其实如果把每个数的二进制位反过来一下,就变成顺序的了!



把二进制反过来其实也不复杂,就一行代码

rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1))

其实这段代码的意义大概是把除了最低位之外的位用i>>1翻转,然后再将最低位移到最高位

也就是说,做FFT的时候只要将系数重新排列,然后每段进行合并更新就好啦(这段看一下代码就会很清楚)

IFFT#

IFFT(Inverse Discrete Fourier Transform)快速傅立叶逆变换

实际上我们的傅立叶变换可以写成矩阵的形式:

\(\left[ {\begin{array}{*{20}{c}}
1&1&1&{...}&1\\
1&{\omega _n^1}&{\omega _n^2}&{...}&{\omega _n^{n - 1}}\\
1&{\omega _n^2}&{\omega _n^4}&{...}&{\omega _n^{2n - 2}}\\
{...}&{...}&{...}&{...}&{...}\\
1&{\omega _n^{n - 1}}&{\omega _n^{2n - 2}}&{...}&{\omega _n^{(n - 1) \times (n - 1)}}
\end{array}} \right]\left[ {\begin{array}{*{20}{c}}
{{a_0}}\\
{{a_1}}\\
{{a_2}}\\
{...}\\
{{a_{n - 1}}}
\end{array}} \right] = \left[ {\begin{array}{*{20}{c}}
{f(\omega _n^0)}\\
{f(\omega _n^1)}\\
{f(\omega _n^2)}\\
{...}\\
{f(\omega _n^{n - 1})}
\end{array}} \right]\)

当然,看到这个形式,我们第一个想法就是对

\(\left[ {\begin{array}{*{20}{c}}
1&1&1&{...}&1\\
1&{\omega _n^1}&{\omega _n^2}&{...}&{\omega _n^{n - 1}}\\
1&{\omega _n^2}&{\omega _n^4}&{...}&{\omega _n^{2n - 2}}\\
{...}&{...}&{...}&{...}&{...}\\
1&{\omega _n^{n - 1}}&{\omega _n^{2n - 2}}&{...}&{\omega _n^{(n - 1) \times (n - 1)}}
\end{array}} \right]\)

这个矩阵求逆,但是这样的话,复杂度就吃不消了,我们好不容易把\(O(n^2)\)的DFT降为\(O(nlogn)\)的FFT,结果逆不回去,这不是非常尴尬吗

但是我们发现,我们对这个矩阵求一下逆,可以得到这个形式是固定的

\(\left[ {\begin{array}{*{20}{c}}
{\frac{1}{n}}&{\frac{1}{n}}&{\frac{1}{n}}&{...}&{\frac{1}{n}}\\
{\frac{1}{n}}&{\frac{1}{n}\omega _n^{ - 1}}&{\frac{1}{n}\omega _n^{ - 2}}&{...}&{\frac{1}{n}\omega _n^{1 - n}}\\
{\frac{1}{n}}&{\frac{1}{n}\omega _n^{ - 2}}&{\frac{1}{n}\omega _n^{ - 4}}&{...}&{\frac{1}{n}\omega _n^{2 - 2n}}\\
{...}&{...}&{...}&{...}&{...}\\
{\frac{1}{n}}&{\frac{1}{n}\omega _n^{1 - n}}&{\frac{1}{n}\omega _n^{2 - 2n}}&{...}&{\frac{1}{n}\omega _n^{ - (n - 1) \times (n - 1)}}
\end{array}} \right]\)

然后我们可以把\(\frac{1}{n}\)提出来,那么我们就可以先利用快速傅立叶变换,做出系数的\(n\)倍,然后再除掉

坑终于填完啦

FFT_Code#

这是一个高精乘的模板

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
char s[60050],s1[60050];
struct r{
double real,image;
}a[131090],b[131090];
double pi=acos(-1);
r operator +(r a,r b){return (r){a.real+b.real,b.image+a.image};}
r operator -(r a,r b){return (r){a.real-b.real,a.image-b.image};}
r operator *(r a,r b){return (r){a.real*b.real-b.image*a.image,a.real*b.image+a.image*b.real};}
int ans[131090],rev[131090],bit,n,l;
void fft(r *a,int n,int op){
for (int i=0;i<n;i++)if (i<rev[i])std::swap(a[rev[i]],a[i]);
for (int i=1;i<n;i<<=1){
r wn=(r){cos(pi/i),op*sin(pi/i)};//将单位圆分成i*2个部分
for (int j=0;j<n;j+=i<<1){
r wnk=(r){1,0};
for (int k=j;k<i+j;k++,wnk=wnk*wn){
r x=a[k],y=wnk*a[i+k];
a[k]=x+y;
a[k+i]=x-y;//这里很蝴蝶
}
}
}
if (op==-1)for (int i=0;i<n;i++)a[i].real/=n;
}
int main(){
scanf("%d",&l);
scanf("%s",s);
scanf("%s",s1);
int n=2;
for (bit=1;(1<<bit)<(l<<1)-1;bit++)n<<=1;
for (int i=0;i<l;i++)a[i]=(r){(double)(s[l-i-1]-'0'),0};
for (int i=0;i<l;i++)b[i]=(r){(double)(s1[l-i-1]-'0'),0};
for (int i=0;i<n;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));//处理rev数组
fft(a,n,1);
fft(b,n,1);
for (int i=0;i<n;i++)a[i]=a[i]*b[i];
fft(a,n,-1); //IFFT
for (int i=0;i<n;i++){
ans[i]+=(a[i].real+0.5);
ans[i+1]=ans[i]/10;
ans[i]%=10;
}
int i;
for (i=n;i>=0&&!ans[i];i--);
if (i<0)putchar('0');
else for (;i>=0;i--)printf("%d",ans[i]);
}

什么是NTT#

NTT(Number Theoretic Transform)快速数论变换(不要问我快速是哪来的,我不知道)

可以将类似FFT的做法用整数来做

与FFT区别##

1、可以取模(必须取模)

2、比FFT快这是一个遥远的传说,究竟哪个快还是和数据有关

原根#

对于一个正整数m,存在一个整数g,满足\(g^{\varphi (m)}\equiv 1(mod\) \(m)\),则g为m的一个原根

一个数存在原根的充要条件是该数可以表示为\(2,4,p^k,2*p^k\)(\(p\)为奇质数,\(k \geq 1\))

NTT#

有了原根我们就可以考虑用原根来代替单位根,因为原根也有类似的性质(一般此类题目模数为质数,所以以下用\(p-1\)代替\(\varphi(p)\))

那么我们考虑用\(G_n^i\)=\(g^{i(p-1)/n}\)来代替\(\omega _n^i\)

为什么这样代替呢,因为这样代替就满足:

\(G_n^0=G_n^n=1\),这个显然吧?

\(G_n^i=-G_n^{i+n/2}\),这个通过原根的定义稍微推一下就能得到

\(G _{2n}^{2i}=G _n^i\),这个应该问题也不大吧?

然后我们就发现NTT它锅了,哪里锅了呢?

性质当然是没有问题的,锅就锅在\(n \nmid (p-1)\)的时候,我们没办法做浮点数的快速幂

那我们该怎么办,难道抛弃原根去寻找新方法?

Of course not.我们就规定NTT的模数只能取\(k*2^m+1\)并且\(n \leq 2^m\)就行啦(最常用的可以NTT的模数就是998244353,如果出现新的大家自己检验一下啦)

(顺便送给各位,998244353的一个原根是3是不是非常良心)

NTT_Code#

这还是一个高精乘的模板

#include<cstdio>
#include<algorithm>
int g=3,mo=998244353,a[131090],b[131090],rev[131090],l,bit;
char s[60050],s1[60050];
int ksm(int p,int k){
int ret=1;
while (k){
if ((k&1))ret=(ret*1ll*p)%mo;
p=(p*1ll*p)%mo;k>>=1;
}
return ret;
}
void ntt(int *a,int n,int f){
for (int i=0;i<n;i++)if (i<rev[i])std::swap(a[i],a[rev[i]]);
for (int i=1;i<n;i<<=1){
int gn=ksm(g,(mo-1)/(i<<1));if (f==-1)gn=ksm(gn,mo-2);
for(int j=0;j<n;j+=i<<1){
int gqn=1;
for (int k=j;k<j+i;k++){
int x=a[k],y=gqn*1ll*a[k+i]%mo;
a[k]=(x+y)%mo;
a[k+i]=(x-y+mo)%mo;
gqn=(gqn*1ll*gn)%mo;
}
}
}
}
int main(){
scanf("%d",&l);
scanf("%s",s);
scanf("%s",s1);
int n=2;
for (bit=1;(1<<bit)<(l<<1)-1;bit++)n<<=1;
for (int i=0;i<l;i++)a[i]=s[l-i-1]-'0';
for (int i=0;i<l;i++)b[i]=s1[l-i-1]-'0';
for (int i=0;i<n;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
ntt(a,n,1);
ntt(b,n,1);
for (int i=0;i<n;i++)a[i]=(a[i]*1ll*b[i])%mo;
ntt(a,n,-1);
int ny=ksm(n,mo-2);
for (int i=0;i<n;i++)a[i]=(a[i]*1ll*ny)%mo;
for (int i=0;i<n;i++)a[i+1]+=a[i]/10,a[i]%=10;
int i;
for (i=n;i>=0&&!a[i];i--);
if (i==-1)printf("0");
else for (;i>=0;i--)printf("%d",a[i]);
}

一点小应用#

多项式求逆##

多项式求逆是个啥呢,就是我们有多项式\(f(x)\)我们希望求出多项式\(g(x)\)满足

\(f(x)g(x)\equiv 1(mod\) \(x^n)\)

假设我们现在已经得到了\(p(x)\)满足\(f(x)p(x)\equiv 1(mod\) \(x^{\left \lceil \frac{n}{2} \right \rceil})\)

因为\(g(x)\)满足\(f(x)g(x)\equiv 1(mod\) \(x^n)\),则必然满足\(f(x)g(x)\equiv 1(mod\) \(x^{\left \lceil \frac{n}{2} \right \rceil})\)

所以\(f(x)(g(x)-p(x))\equiv 0(mod\) \(x^{\left \lceil \frac{n}{2} \right \rceil})\)

因为\(f(x)\not\equiv 0(mod\) \(x^{\left \lceil \frac{n}{2} \right \rceil})\)(否则不存在\(g(x)\),可以证明这样的多项式不存在逆多项式)

所以\(g(x)-p(x)\equiv 0(mod\) \(x^{\left \lceil \frac{n}{2} \right \rceil})\)

将其平方一下得到:

\(g^2(x)-2g(x)p(x)+p^2(x)\equiv 0(mod\) \(x^n)\)(为什么后面mod的东西也平方了?因为\(x\)次多项式和\(y\)次多项式相乘后得到的是\(x+y\)次的多项式)

\(g^2(x)\equiv 2g(x)p(x)-p^2(x)(mod\) \(x^n)\)

\(g(x)\equiv 2p(x)-\frac{p^2(x)}{g(x)}(mod\) \(x^n)\)

当然\(f(x)\)与\(g(x)\)是互逆的,所以\(g(x)\equiv 2p(x)-f(x)p^2(x)(mod\) \(x^n)\)

所以\(g(x)\equiv p(x)(2-f(x)p(x))(mod\) \(x^n)\)

到这里,我们发现,我们只要能够求出\(p(x)\)就可以求出\(g(x)\),那递归求解就好啦

void inv(int *b,int n){
if (n==1){a[0]=ksm(b[0],mo-2);return;}
inv(b,(n+1)>>1);
int bit,len=2;
for (bit=1;(1<<bit)<(n<<1);bit++)len<<=1;
for (int i=0;i<n;i++)d[i]=b[i];
for (int i=n;i<len;i++)d[i]=0;
for (int i=0;i<len;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
ntt(a,len,1);ntt(d,len,1);
for (int i=0;i<len;i++)a[i]=(2ll-d[i]*1ll*a[i]%mo+mo)%mo*1ll*a[i]%mo;
ntt(a,len,-1);
for (int i=n;i<len;i++)a[i]=0;
}

多项式取ln##

相信大家已经熟练掌握了FFT(可能我还没?)我们就来学习一下多项式取ln的艺术吧

对于一个多项式\(f(x)\),我们想求出\(ln(f(x))\)

令\(g(x)=ln(f(x))\),两边同时求导得:

\(g'(x)=\frac{f'(x)}{f(x)}\)

那么\(g(x)=\int \frac{f'(x)}{f(x)}\)

那我们就只要做一下多项式求导,多项式求逆,多项式积分就好啦

FFT学习及简单应用(一点点详细)的更多相关文章

  1. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  2. JMeter学习工具简单介绍

    JMeter学习工具简单介绍   一.JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静态 ...

  3. [大坑]FFT学习

    [大坑]FFT学习 Macros #define fon(i,s) for(int i=0;i<s; ++i) #define fone(i,s) for(int i=0;i<=s;++i ...

  4. Qt简单项目--加法计算器(详细代码注释)

    Qt的简单案例--加法计算器(详细代码注释) 一.项目结构 二.项目代码 widget.h #ifndef WIDGET_H #define WIDGET_H //预编译指令, 为了避免头文件被重复包 ...

  5. 快速傅里叶变换(FFT)学习笔记

    定义 多项式 系数表示法 设\(A(x)\)表示一个\(n-1\)次多项式,则所有项的系数组成的\(n\)维向量\((a_0,a_1,a_2,\dots,a_{n-1})\)唯一确定了这个多项式. 即 ...

  6. (java)selenium webdriver学习---实现简单的翻页,将页面内容的标题和标题链接取出

    selenium webdriver学习---实现简单的翻页,将页面内容的标题和标题链接取出: 该情况适合能能循环page=1~n,并且每个网页随着循环可以打开的情况, 注意一定是自己拼接的url可以 ...

  7. IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试

    IIC驱动学习笔记,简单的TSC2007的IIC驱动编写,测试 目的不是为了编写TSC2007驱动,是为了学习IIC驱动的编写,读一下TSC2007的ADC数据进行练习,, Linux主机驱动和外设驱 ...

  8. 快速傅里叶变换(FFT)学习笔记(其一)

    再探快速傅里叶变换(FFT)学习笔记(其一) 目录 再探快速傅里叶变换(FFT)学习笔记(其一) 写在前面 为什么写这篇博客 一些约定 前置知识 多项式卷积 多项式的系数表达式和点值表达式 单位根及其 ...

  9. 快速傅里叶变换(FFT)学习笔记(其二)(NTT)

    再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...

随机推荐

  1. msp430入门编程05

    msp430中C语言的运算符和表达式 msp430中C语言的程序结构06 msp430中C语言的函数及实现07 msp430中C语言操作端口I/O10 msp430中C语言的模块化头文件及实现11 m ...

  2. POJ 2101 Intervals 差分约束

    Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 27746   Accepted: 10687 Description You ...

  3. 洛谷 P2064 奇妙的汽车

    P2064 奇妙的汽车 题目描述 你有着一辆奇妙的汽车,这辆汽车有着自动加速的功能.打个比方吧,第1天你驾驶着它可以行驶a路程,那么第2天你可以让它所走的路程增加到第1天的2~9倍(必须是其中一个整数 ...

  4. 一 hadoop 相关介绍

    hadoop 相关介绍 hadoop的首页有下面这样一段介绍.对hadoop是什么这个问题,做了简要的回答. The Apache™ Hadoop® project develops open-sou ...

  5. MapReduce的Reduce side Join

    1. 简单介绍 reduce side  join是全部join中用时最长的一种join,可是这样的方法可以适用内连接.left外连接.right外连接.full外连接和反连接等全部的join方式.r ...

  6. IOS推断是否安装微信qq

    BOOL weicaht = [[UIApplicationsharedApplication]canOpenURL:[NSURLURLWithString:@"mqqapi://" ...

  7. 关于使用data()获取自定义属性出现undefined的说明

    这应该是这个函数的一个bug,没有考虑到驼峰式的写法,当我写成驼峰式,即是有大小写的变量时就会出现没有定义的情况. 今天写个交互,需要用到自定义属性,因为这个自定义属性是当作字段用的,就直接用了字段名 ...

  8. muduo源代码分析--Reactor模式在muduo中的使用

    一. Reactor模式简单介绍 Reactor释义"反应堆",是一种事件驱动机制.和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完毕处理.而是恰恰相反.React ...

  9. AVAudioSessionCategory的选择

    AVAudioSessionCategoryAmbient 或 kAudioSessionCategory_AmbientSound --用于非以语音为主的应用,使用这个category的应用会随着静 ...

  10. android不是内部或外部命令,也不是可执行的程序或批处理文件

    问题: 原因:没有配置好android sdk环境变量 解决方法: (1)切换到android sdk下的tools文件夹,再执行android命令就可以启动Android SDK管理器,我的andr ...