学了好久,终于基本弄明白了

推荐两个博客:

戳我

戳我

再推荐几本书:

《ACM/ICPC算法基础训练教程》

《组合数学》(清华大学出版社)

《高中数学选修》

预备知识

复数方面

找数学老师去

\[i^{2}=-1,i为虚数的单位
\]

坐标系上纵轴就是虚数轴,复数就是这上面的点

三种表示法:

$$一般:a + bi,a为实部,b为虚部$$

$$指数:e^{i\theta}坐标系上的模长$$

$$三角:模长
(cos\theta + i sin \theta)$$

运算:

加减法:实部虚部分别相加

乘法:$$(a + bi) * (c + di) = ac + adi + bci + bdi^{2}

= ac-bd+(ad+bc)i$$

欧拉公式

\[e^{ix} = cosx + isinx(就是指数表示和三角表示)
\]

\[特别的e^{i\pi} = -1
\]

多项式

\[系数表示法:A(x) = \Sigma _{k=0}^{n - 1} a_kx^k
\]

\[点值表示法:对于所有的x_k,求出它们对应的A(x),设为y_k
\]

\[则可以用\{(x_0, y_0), (x_1, y_1), ......, (x_n-1, y_n-1)\} 表示这个多项式
并且是唯一确定的\]

单位复数根

\[n次单位复数根\omega^{n} = 1,n次单位复数根刚好有n个对应e^{\frac{2k\pi i}{n}},其中k=0到n - 1
\]

三个性质:

消去引理:

$$n, d, k为正整数,则\omega{dk}_{dn}=\omega{k}{n}$$

$$证明:套e^{\frac{2k\pi i}{n}} 即可$$

折半引理:

$$n为大于零的偶数,则(\omega{k+\frac{n}{2}}_{n}){2}=\omega{2k+n}_{n}=\omega{2k}
{n}\omega{n}_{n}=(\omega{k}{n})^{2}$$

求和引理:

大于1的整数n,和不被n整除的非负整数k,有

$$\Sigma{n-1}_{j=0}(\omega{k}
{n})^{j}=0$$

证明可以用等比数列求和公式得到(很简单的,手推一遍就好)

Rader排序

其实就是二进制数位翻转

正题

DFT

对于k=0~n-1,定义:

\[y_k=A(\omega^{k}_{n}) = \Sigma^{n-1}_{j=0} a_j(\omega^{k}_{n})^j
\]

\[得到的y称为a的离散傅里叶变换,记作y=DFT_n(a) (这里的y,a指的是所有的y_k, a_k,即向量y,a)
\]

逆DFT

\[就是DFT的逆变换,求出向量a,记为DFT^{-1}
\]

假设得到了向量y

\[对于y_k = \Sigma^{n-1}_{i=0}a_i(\omega^{k})^i
\]

\[有a_k = \frac{1}{n}\Sigma^{n-1}_{i=0}y_i(\omega^{-k})^i
\]

\[证明:a_k=\frac{1}{n}\Sigma^{n-1}_{i=0}y_i(\omega^{-k})^i=\frac{1}{n}\Sigma^{n-1}_{i=0}( \Sigma^{n-1}_{j=0}a_j(\omega^{k})^j)(\omega^{-k})^i=\frac{1}{n} \Sigma^{n-1}_{i=0}a_i(\Sigma^{n-1}_{j=0}(\omega^{j-k})^i)
\]

\[可以用等比数列求和出上面的就是a_k(当j\ne k是括号里的为0,当j=k时为1)
\]

FFT

上面已经把DFT和逆DFT搞定了,两个几乎是一样的

所以求多项式的积(卷积)可以用DFT转换成点值表示,就可以O(n),一一相乘,得到积的多项式的点值表示,最后用逆DFT得到系数表示

复杂度瓶颈在于怎样快速求解DFT(逆DFT和DFT方法一样)

FFT就是一个O(nlogn)求解DFT的方法

首先把A(x)分成奇数项和偶数项记作

\[A^{[0]}(x) = a_0 + a_2x + a_4x^2 + ... + a_{n-2}x^{\frac{n}{2} - 1}
\]

\[A^{[1]}(x) = a_1 + a_3x + a_5x^2 + ... + a_{n-1}x^{\frac{n}{2} - 1}
\]

\[显然A(x) = A^{[0]}(x^2) + xA^{[1]}(x^2)
\]

那么

\[A(\omega^k_n)=A^{[0]}((\omega^k_n)^2) + \omega^k_n A^{[1]}((\omega^k_n)^2)=A^{[0]}(\omega^k_{\frac{n}{2}}) + \omega^k_n A^{[1]}(\omega^k_{\frac{n}{2}})
\]

\[因为\omega^{\frac{n}{2}}_{n}=\omega_{2}=e^{k\pi i}= cos k\pi + i sin k\pi = -1
\]

\[所以A(\omega^{k+\frac{n}{2}}_n)=A^{[0]}(\omega^k_{\frac{n}{2}}) - \omega^k_n A^{[1]}(\omega^k_{\frac{n}{2}})
\]

这称为蝴蝶操作

于是对每个y值的求解可以通过分组求出,若递归变成处理子任务,这样复杂度就成了O(nlogn)

这样不停地分组,最后就相当于Rader排序了一番,所以也可以变成非递归的

注意每次都要把多项式补成2的幂,便于FFT

递归写可能好理解一些,但不好写

还有一些东西什么的,其实记一记就好了其实自己说不清

系统的复数complex代码

  1. # include <bits/stdc++.h>
  2. # define RG register
  3. # define IL inline
  4. # define Fill(a, b) memset(a, b, sizeof(a))
  5. using namespace std;
  6. typedef long long ll;
  7. const int _(3e6 + 10);
  8. const double Pi = acos(-1);
  9. IL ll Read(){
  10. char c = '%'; ll x = 0, z = 1;
  11. for(; c > '9' || c < '0'; c = getchar()) if(c == '-') z = -1;
  12. for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
  13. return x * z;
  14. }
  15. int n, m, r[_], l;
  16. complex <double> a[_], b[_];
  17. IL void FFT(complex <double> *P, int opt){
  18. for(RG int i = 0; i < n; ++i) if(i < r[i]) swap(P[i], P[r[i]]); //Rader排序
  19. for(RG int i = 1; i < n; i <<= 1){
  20. complex <double> W(cos(Pi / i), opt * sin(Pi / i)); //旋转因子
  21. for(RG int p = i << 1, j = 0; j < n; j += p){
  22. complex <double> w(1, 0);
  23. for(RG int k = 0; k < i; ++k, w *= W){
  24. complex <double> X = P[j + k], Y = w * P[j + k + i];
  25. P[j + k] = X + Y; P[j + k + i] = X - Y; //蝴蝶操作
  26. }
  27. }
  28. }
  29. }
  30. int main(RG int argc, RG char *argv[]){
  31. n = Read(); m = Read();
  32. for(RG int i = 0; i <= n; ++i) a[i] = Read();
  33. for(RG int i = 0; i <= m; ++i) b[i] = Read();
  34. m += n;
  35. for(n = 1; n <= m; n <<= 1) ++l;//补成2的幂
  36. for(RG int i = 0; i < n; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));//Rader排序预处理
  37. FFT(a, 1); FFT(b, 1); //DFT
  38. for(RG int i = 0; i < n; ++i) a[i] = a[i] * b[i]; //点值直接相乘
  39. FFT(a, -1); //逆DFT
  40. for(RG int i = 0; i <= m; ++i) printf("%d ", (int)(a[i].real() / n + 0.5));
  41. return 0;
  42. }

或者可以自己定义complex,用复数运算

  1. struct Complex{
  2. double real, image;
  3. IL Complex(){ real = image = 0; }
  4. IL Complex(RG double a, RG double b){ real = a; image = b; }
  5. IL Complex operator +(RG Complex B){ return Complex(real + B.real, image + B.image); }
  6. IL Complex operator -(RG Complex B){ return Complex(real - B.real, image - B.image); }
  7. IL Complex operator *(RG Complex B){ return Complex(real * B.real - image * B.image, real * B.image + image * B.real); }
  8. }

NTT(快速数论变换)

前置技能原根

设\(g\)为\(p\)(质数)的原根

则\(e^{\frac{2\pi i}{n}}\equiv\omega_n\equiv g^{\frac{p-1}{n}}(mod \ p)\)

带进去就好了

Reverse的那个不会证明

\(UOJ\)的模板

  1. # include <bits/stdc++.h>
  2. # define RG register
  3. # define IL inline
  4. # define Fill(a, b) memset(a, b, sizeof(a))
  5. using namespace std;
  6. typedef long long ll;
  7. const int Zsy(998244353);
  8. const int Phi(998244352);
  9. const int G(3);
  10. const int _(4e5 + 5);
  11. IL ll Input(){
  12. RG ll x = 0, z = 1; RG char c = getchar();
  13. for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
  14. for(; c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
  15. return x * z;
  16. }
  17. int n, m, N, l, r[_], A[_], B[_];
  18. IL int Pow(RG ll x, RG ll y){
  19. RG ll ret = 1;
  20. for(; y; y >>= 1, x = x * x % Zsy)
  21. if(y & 1) ret = ret * x % Zsy;
  22. return ret;
  23. }
  24. IL void NTT(RG int *P, RG int opt){
  25. for(RG int i = 0; i < N; ++i) if(r[i] < i) swap(P[r[i]], P[i]);
  26. for(RG int i = 1; i < N; i <<= 1){
  27. RG int W = Pow(G, Phi / (i << 1));
  28. if(opt == -1) W = Pow(W, Zsy - 2);
  29. for(RG int j = 0, p = i << 1; j < N; j += p){
  30. RG int w = 1;
  31. for(RG int k = 0; k < i; ++k, w = 1LL * w * W % Zsy){
  32. RG int X = P[k + j], Y = 1LL * w * P[k + j + i] % Zsy;
  33. P[k + j] = (X + Y) % Zsy, P[k + j + i] = (X - Y + Zsy) % Zsy;
  34. }
  35. }
  36. }
  37. }
  38. int main(RG int argc, RG char* argv[]){
  39. n = Input(), m = Input();
  40. for(RG int i = 0; i <= n; ++i) A[i] = Input();
  41. for(RG int i = 0; i <= m; ++i) B[i] = Input();
  42. for(n += m, N = 1; N <= n; N <<= 1) ++l;
  43. for(RG int i = 0; i < N; ++i) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
  44. NTT(A, 1); NTT(B, 1);
  45. for(RG int i = 0; i < N; ++i) A[i] = 1LL * A[i] * B[i] % Zsy;
  46. NTT(A, -1);
  47. RG int inv = Pow(N, Zsy - 2);
  48. for(RG int i = 0; i <= n; ++i) printf("%lld ", 1LL * A[i] * inv % Zsy);
  49. return 0;
  50. }

FFT\NTT总结的更多相关文章

  1. FFT \ NTT总结(多项式的构造方法)

    前言.FFT  NTT 算法 网上有很多,这里不再赘述. 模板见我的代码库: FFT:戳我 NTT:戳我 正经向:FFT题目解题思路 \(FFT\)这个玩意不可能直接裸考的..... 其实一般\(FF ...

  2. [学习笔记&教程] 信号, 集合, 多项式, 以及各种卷积性变换 (FFT,NTT,FWT,FMT)

    目录 信号, 集合, 多项式, 以及卷积性变换 卷积 卷积性变换 傅里叶变换与信号 引入: 信号分析 变换的基础: 复数 傅里叶变换 离散傅里叶变换 FFT 与多项式 \(n\) 次单位复根 消去引理 ...

  3. FFT/NTT/MTT学习笔记

    FFT/NTT/MTT Tags:数学 作业部落 评论地址 前言 这是网上的优秀博客 并不建议初学者看我的博客,因为我也不是很了解FFT的具体原理 一.概述 两个多项式相乘,不用\(N^2\),通过\ ...

  4. FFT&NTT总结

    FFT&NTT总结 一些概念 \(DFT:\)离散傅里叶变换\(\rightarrow O(n^2)\)计算多项式卷积 \(FFT:\)快速傅里叶变换\(\rightarrow O(nlogn ...

  5. 快速构造FFT/NTT

    @(学习笔记)[FFT, NTT] 问题概述 给出两个次数为\(n\)的多项式\(A\)和\(B\), 要求在\(O(n \log n)\)内求出它们的卷积, 即对于结果\(C\)的每一项, 都有\[ ...

  6. FFT/NTT模板 既 HDU1402 A * B Problem Plus

    @(学习笔记)[FFT, NTT] Problem Description Calculate A * B. Input Each line will contain two integers A a ...

  7. FFT/NTT基础题总结

    在学各种数各种反演之前把以前做的$FFT$/$NTT$的题整理一遍 还请数论$dalao$口下留情 T1快速傅立叶之二 题目中要求求出 $c_k=\sum\limits_{i=k}^{n-1}a_i* ...

  8. $FFT/NTT/FWT$题单&简要题解

    打算写一个多项式总结. 虽然自己菜得太真实了. 好像四级标题太小了,下次写博客的时候再考虑一下. 模板 \(FFT\)模板 #include <iostream> #include < ...

  9. FFT&NTT数学解释

    FFT和NTT真是噩梦呢 既然被FFT和NTT坑够了,坑一下其他的人也未尝不可呢 前置知识 多项式基础知识 矩阵基础知识(之后会一直用矩阵表达) FFT:复数基础知识 NTT:模运算基础知识 单位根介 ...

  10. HDU-4609(FFT/NTT)

    HDU-4609(FFT/NTT) 题意: 给出n个木棒,现从中不重复地选出3根来,求能拼出三角形的概率. 计算合法概率容易出现重复,所以建议计算不合法方案数 枚举选出的最大边是哪条,然后考虑剩下两条 ...

随机推荐

  1. Redis 学习(二) —— 数据类型及操作

    Redis支持string.list.set.zset.hash等数据类型,这一篇学习redis的数据类型.命令及某些使用场景. 一.String,字符串 字符串是 Redis 最基本的数据类型.一个 ...

  2. css scale 元素放大缩小效果

    <style> .trans-scale { width: 300px; height:300px; margin:100px auto; background:#99F; transit ...

  3. java5 - 数组与排序算法

    数组是什么? 一.一维数组 1 声明与定义的区别 一般的情况下我们常常这样叙述, 把建立空间的声明称之为"定义", 而把不需要建立存储空间称之为"声明". 很明 ...

  4. 前端JS面试题汇总 Part 2 (null与undefined/闭包/foreach与map/匿名函数/代码组织)

    原文:https://github.com/yangshun/front-end-interview-handbook/blob/master/questions/javascript-questio ...

  5. 用Composer获取第三方资源总是失败咋办?

    凉拌!!! 不不不,哥可是一个有追求的人,没那么容易放弃的! 所以我选择用中国全量镜像,https://pkg.phpcomposer.com/ 使用方法: 对,就是命令行方法,我最喜欢的方法!!! ...

  6. windows转mac-开发环境搭建(一):需要搭建的环境及安装的工具

    作为一个java后端开发者来说,随着项目的增加,前段时间用windows真是受尽折磨,电脑卡到不行,在我们开发部技术大佬的一再安利之下,狠下心选了个17年13寸带touch bar的MacBook P ...

  7. Google2016 面试题 吹气球 区间dp

    题意:有n个气球,编号为0到n-1,每个气球都有一个分数,存在nums数组中.每次吹气球i可以得到的分数为 nums[left] * nums[i] * nums[right],left和right分 ...

  8. uva1625

    思路:每次选择颜色面临有两个选择:1.序列A的首部颜色 2.序列B的首部元素,定义状态d[i][j]表示A序列已经选取了前i个颜色,B序列已经选取了前j个颜色的情况下最小的L(c)总和. 状态转移:c ...

  9. BZOJ4554 - [TJOI2016&HEOI2016]游戏

    原题链接 Description 给出一个的地图,地图上有空地.软石头和硬石头.求在这张地图上最多能放上多少个炸弹能使得任意两个炸弹之间不会互相炸到.炸弹能炸到的范围是该炸弹所在的一行和一列,炸弹的威 ...

  10. Docker系列三:Docker容器管理

    Docker容器管理 1. 单一容器管理 1) 容器的启动 $ docker run --name gitlab-redis -d --volume /srv/docker/gitlab/redis: ...