重探 DFT
感觉上次学习DFT简直是乱来了。不知道误导了多少人,这里深感抱歉。
这次我再看了看《算法导论》,觉得收获很大,终于粗略的知道DFT的原理了!
如何将两个多项式相乘
对于一个n次多项式,\(f(x)=\sum_{i=0}^n a_ix^i\),根据书上说的是有一个叫插值的东西。通俗的说,就是我们随意选取\(n\)个不同的\(x\),不妨设为\(x_0,x_1,...,x_n\),通过这个我们可以算出\(y_i=f(x_i)\),这个过程就是DFT。
我们现在需要将两个多项式相乘,所以这两个多项式都算出它们的\(y_i\),分别记为\(y_i^{[0]}\)和\(y_i^{[1]}\),然后我们得到\(y_i=y_i^{[0]}y_i^{[1]}\),然后将\(y\)进行IDFT就是答案了。根据插值多项式的唯一性,可以证明方法的正确性。
这里要注意的是,如果原来的两个多项式的项数分别为\(n,m\),则我们要取\(2^s(s\in Z^+,2^s>n+m)\)个不同的\(x\)。
如何采用分治的方法计算DFT
如果我们要计算$$f(x)=\sum_{i=0}{n-1}a_ixi$$
这里的\(n=2^s(s\in Z^+)\)。
设$$g(x)=\sum_{i=0}{n/2-1}a_{2i}xi$$
\]
那么\(f(x)=g(x^2)+xh(x^2)\)。既然要分治,我们需要把问题的规模减少一半,但是\(x^2\)仍有\(n\)个取值。
为了减少\(x^2\)的取值,\(x\)的取值中我们可以有\(n/2\)对相反数,这样它们的平方是相同的。我们在确定这一轮\(x\)的取值后,下一轮\(x\)的取值就确定了。所以,如果我们简单的取\(x=1,-1,2,-2,3,-3,...\),下一轮的\(x=1,4,9,...\),就没有相反数了。
为了解决这个问题,单位复数根来了。
设\(\omega_n^k=e^{2\pi ki /n}\),\(i\)是虚数单位。
如果这一轮我们的\(x=\omega_n^k(k=0,1,2,...,n-1)\),那么\(\omega_n^k\)与\(\omega_n^{k+n/2}(k=0,1,2,...,n/2-1)\)将会是相反数。
而且,下一轮的\(x=\omega_{n/2}^{k}(k=0,1,2,...,n/2-1)\)将有\(n/4\)对相反数,问题规模恰好缩小为原来的一半。
如此,便可以得到以下代码:
fft(a)
n = a.length
if n == 1
return a
wn = cos(2 * PI / n) + sin(2 * PI / n)
w = 1
a0 = (a[0],a[2],a[4],...,a[n-2])
a1 = (a[1],a[3],a[5],...,a[n-1])
y0 = fft(a0)
y1 = fft(a1)
for k = 0 to n / 2 - 1
y[k] = y0[k] + w * y1[k]
y[k + n / 2] = y0[k] - w * y1[k]
w = w * wn
return y
那么IDFT?
我们上面的计算过程即是:
不会画矩阵,只好用表格替代了。
然后我们只要求出那个\(n\times n\)的矩阵的逆矩阵就可以了。
设原来的矩阵\(V_n\)的\((k,j)\)处的元素为\(\omega_n^{kj}\),可以证明,\(V_n^{-1}\)的\((j,k)\)处的元素为\(\omega_n^{-kj}/n\)。
证明:考虑\((j,j')\)处的元素,$$\sum_{k=0}{n-1}(\omega_n{-kj}/n)(\omega_n{kj'})=\sum_{i=0}{n-1}\omega_n^{k(j'-j)}/n$$
如果\(j=j'\),那么结果显然是\(1\),否则,是\(0\),因为它们都有自己的相反数,可以消掉。
这样就证明了它们的乘积是一个单位矩阵。
可以发现,我们只需要把原来的\(\omega_n^{kj}\)变为\(\omega_n^{-kj}\)就可以了,记得最后要除以\(n\)。这样的计算过程和原来的分治方法是一样的。
蝴蝶变换
其实这是一个对于OI来说不可忽视的常数优化。
我们来跟踪一下输入fft第\(i\)位的元素到了哪个位置,可以发现,如果\(i=(11010)\),那么最后到达的位置就是\((01011)\)(这里括号里的是二进制),就是把它的二进制翻转。于是就有下面的代码:
一开始要把元素移动到对应的位置。
void fft(cpd* A, bool rev) {
for (int i = 0; i < n; i ++)
if (bitRev[i] < i) swap(A[i], A[bitRev[i]]);
for (int s = 1; s <= MAXLOG; s ++) {
int m = 1 << s;
cpd unit(cos(PI * 2 / m), sin(PI * 2 / m) * (rev ? -1 : 1));
for (int k = 0; k + m <= n; k += m) {
cpd w(1, 0);
for (int j = 0; j < m >> 1; j ++) {
cpd t = w * A[k + j + (m >> 1)];
cpd u = A[k + j];
A[k + j] = u + t;
A[k + j + (m >> 1)] = u - t;
w *= unit;
}
}
}
if (rev)
for (int i = 0; i < n; i ++) A[i] /= n;
}
模下的
一般模数为\(b2^a+1\)而且是个质数,例子\(7×17×2^{23}+1=998244353\)。
同样的,我们要找到\(n\)个不同的\(x\),也就是说,我们要找到一个\(x\),使得\(x^n=1\)且\(x^{n/2}=-1\),这里的等号都是模意义下的。
我们可以采用下面的方法找到这样的\(x\):
因为\(p\)是质数,所以\(a^{p-1}=1\)(\(a\)是任意整数),不妨令\(x=y^{(p-1)/n}\),由于\(n\)是\(2\)的幂,所以这里一定有\(n|p-1\)。
接着我们就满足了\(x^n=1\)这一个条件,另外一个的话,我们可以枚举\(y\),然后就可以找到了。
对于\(998244353\),\(y\)可以取\(3\)。
记录下一个求素数\(p\)原根的方法:
由于原根比较小,我们可以从\(2\)开始枚举,现在的问题是如何判断一个数\(a\)是不是\(p\)的原根。
由于\(a^{p-1}=1\),所以我们枚举\(p-1\)的素因子\(f\),如果存在这样的\(f\)使得\(a^{p-1 \over f}=1\)那么\(a\)就不是原根。
重探 DFT的更多相关文章
- C++面向对象程序设计_Part1
目录 C++历史 C++的组成 C++ 与 C 的数据和函数区别 基于对象与面向对象的区别 C++类的两个经典分类 头文件防卫式声明 头文件的布局 类的声明 类模板简介 内联(inline)函数 访问 ...
- 【EXCEL】簡単に重複探し
下記のような表があって.重複があるかどうか探すのが大変と思いますが. 簡単に重複探す方法を紹介します. Step1.重複を探す(例えこちらでは項目)を選択します. Step2.メニューで 条件付き書式 ...
- Smart3D系列教程2之 《为什么三维重建效果这么差?——探探那些被忽略的拍照要求和技巧》
一.照片采集的实用概念 根据照片进行三维重建的过程中,有人没怎么遇到坑,有人被坑的不轻.可能是模型的纹理失真,模型的法线错了,模型会生成我们各种也想不到的结果,那么,是什么导致三维重建效果这么差的?是 ...
- Kubernetes 笔记 08 Deployment 副本管理 重新招一个员工来填坑
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复「1024」即可领取,欢迎大家关注,二维码文末可以扫. Hi,大家好, ...
- [心得] SQL Server Partition(表分區) 資料分佈探討
最近在群裡有個朋友問了個問題是這樣的 用户表有一千多万行,主键是用户ID,我做了分区.但经常查询时,其它的表根据用户ID来关联,这样跨区查询,reads非常高.有什么好的处理办法?不分区的话,索引维护 ...
- 第四节:SignalR灵魂所在Hub模型及再探聊天室样例
一. 整体介绍 本节:开始介绍SignalR另外一种通讯模型Hub(中心模型,或者叫集线器模型),它是一种RPC模式,允许客户端和服务器端各自自定义方法并且相互调用,对开发者来说相当友好. 该节包括的 ...
- ELF格式探析之三:sections
前文链接: ELF格式探析之一:Segment和Section ELF格式探析之二:文件头ELF Header详解 今天我们讲对目标文件(可重定位文件)和可执行文件都很重要的section. 我们在讲 ...
- AutoML初创公司探智立方:模型的物竞天择与适者生存
从回归分析的出现到深度学习的蓬勃发展,这条算法的进化路线与其说是「机器替代人」,不如说是「机器帮助人类完毕我们不擅长的事」. 这份「不擅长」列表里有「不擅长从大量数据中寻找规律」.「不擅长同一时候完毕 ...
- DC综合及仿真验证和DFT测试
综合技术是提高设计产能的一个很重要的技术,没有综合技术的发展就不可能用HDL实现电路的设计,因为HDL开始是用来供电路仿真建模使用的,到了后来才把其中一部分子集作为可综合的语言,也就是R ...
随机推荐
- 优酷DEMO
http://v.youku.com/v_show/id_XMTQxOTc4ODA4OA==.html http://v.youku.com/v_show/id_XMTQxOTc5MjU1Mg==.h ...
- 读取jar包里面的文件
一.最近做项目的时候,师兄要求读取jar包里面的java文件.在网上查了各种文件以后,终于完成了,在这里和各位朋友分享一下. (一)找到jar包所在的位置. String path="XXX ...
- gcc代码反汇编查看内存分布[2]: arm-linux-gcc
arm-none-linux-gnueabi-gcc -v gcc version 4.4.1 (Sourcery G++ Lite 2010q1-202) 重点: 代码中的内存分配, 地址从低到高: ...
- select into from 和 insert into select 的用法和区别(转)
转自:http://www.studyofnet.com/news/182.html select into from 和 insert into select都是用来复制表,两者的主要区别为: se ...
- Python之路Day18
今天主要内容:Django Form.Django Admin.Django进阶 Django Admin 自带的验证: Django Admin的定制 Django Admin 自定义actions ...
- 运用Python语言编写获取Linux基本系统信息(三):Python与数据库编程,把获取的信息存入数据库
运用Python语言编写获取Linux基本系统信息(三):Python与数据库编程 有关前两篇的链接: 运用Python语言编写获取Linux基本系统信息(一):获得Linux版本.内核.当前时间 运 ...
- 转:按需加载html 图片 css js
按需加载是前端性能优化中的一项重要措施,按需加载是如何定义的呢?顾名思义,指的是当用户触发了动作时才加载对应的功能.触发的动作,是要看具体的业务场景而言,包括但不限于以下几个情况:鼠标点击.输入文字. ...
- struts文件上传和下载
文件上传 jsp中 <a href="/file/new.action">文件上传案例</a> fileaction中 @Override public S ...
- 基于visual Studio2013解决C语言竞赛题之0407最大值最小值
题目 解决代码及点评 这道题考察循环和比较 /*********************************************************************** ...
- Cocos2d-x 精灵碰撞检測(方法二)
将"Cocos2d-x 精灵碰撞检測(方法一)" update函数改动一下. 使用精灵boundingBox函数获取直接精灵边界框, 不用自己计算精灵矩形大小了,还比較精确,然后调 ...