多项式算法2:NTT(快速数论变换)
多项式算法2:NTT(快速数论变换)
- 前言
- 前置知识
- 正文
前言
算法简介
上次的FFT大量使用浮点数,有精度不好控制的问题,使用可以保证精度的方法又容易超时。这里NTT是在取模的一个前提下找到一个新的且为整数的单位根(在取模意义下),我们记其为GGG,那么用这个根去替代原来的ωn\omega_nωn来进行运算。因为所有运算就会变成整数运算,所以精度误差大大降低。使用NTT也方便按照题目要求进行取模运算,缺点就是只能进行以整数为系数的多项式运算,同时数组长度和每一项的值(如果题目原本不要求取模的话)都有限制。
前置知识
阶
若正整数aaa,ppp互质且满足p>1p>1p>1,则:
对于an≡1(modp)a^n \equiv 1(\mod p )an≡1(modp)最小的nnn,我们称之为aaa模ppp的阶,记作δp(a)\delta_p(a)δp(a)。
对于i∈[0,δp(a))i \in [0,\delta_p(a))i∈[0,δp(a)),所有的aimodpa^i \mod paimodp结果互不相同。
我们假设存在两个整数jjj,kkk满足aj≡ak(modp),0≤j<k<δp(a)a^j \equiv a^k (\mod p),0 \le j \lt k \lt \delta_p(a)aj≡ak(modp),0≤j<k<δp(a)则有ak−j≡1(modp)a^{k-j}\equiv 1 (\mod p)ak−j≡1(modp),与阶的定义矛盾。
原根
设nnn是正整数,ggg是整数,若ggg模nnn的阶δn(g)=φ(n)\delta_n(g)= \varphi(n)δn(g)=φ(n)且gcd(g,n)=1\gcd(g,n) =1gcd(g,n)=1,则称ggg为nnn的一个原根。
若gcd(g,n)=1\gcd(g,n) =1gcd(g,n)=1且n>0n>0n>0,则ggg为nnn的一个原根的充要条件为:S={g1,g2,g3,⋯,gφ(n)}S=\{g^1,g^2,g^3,\cdots,g^{\varphi(n)} \}S={g1,g2,g3,⋯,gφ(n)}为nnn的一组简化剩余系(即一组包含所有模nnn后与nnn互质且取模后的数两两不相同的数)。
由上文可知若ggg为原根,则SSS中任意两个元素在模nnn的意义下不同余。
又因为gcd(g,n)=1\gcd(g,n)=1gcd(g,n)=1,故而SSS为nnn的一组简化剩余系。
FFT
FFT详解
正文
关于原根和模数的选择
由于NTT大量二分数组,我们希望选一些形如p=q×2k+1p=q \times 2^k + 1p=q×2k+1的质数ppp,其中qqq为奇自然数,kkk为整数。
我们有以下的原根可以选。
模数 | 原根 | 原根的逆元 | 最大长度 |
---|---|---|---|
469762049=7×226+1469762049=7 \times 2^{26} + 1469762049=7×226+1 | 3 | 156587350 | 2262^{26}226 |
998244353=119×223+1998244353=119 \times 2^{23} + 1998244353=119×223+1 | 3 | 332748118 | 2232^{23}223 |
2281701377=17×227+12281701377=17 \times 2^{27} + 12281701377=17×227+1 | 3 | 760567126 | 2272^{27}227 |
1004535809=479×221+11004535809=479 \times 2^{21} + 11004535809=479×221+1 | 3 | 334845270 | 2212^{21}221 |
DFT
通过观察,我们可以发现原根有一些和复数单位根很像的性质:
我们令nnn为大于111的222的幂,ppp为素数且n∣(p−1)n |(p-1)n∣(p−1),ggg为ppp的一个原根。
我们设gn=gp−1ng_n=g^{\frac{p-1}{n}}gn=gnp−1所以gnn=gn×p−1n=gp−1g_n^n=g^{n \times \frac{p-1}{n}}=g^{p-1}gnn=gn×np−1=gp−1gnn2=gp−12g_n^{\frac{n}{2}}=g^{\frac{p-1}{2}}gn2n=g2p−1ganak=gak(p−1)an=gk(p−1)n=gnkg_{an}^{ak}=g^{\frac{ak(p-1)}{an}}=g^{\frac{k(p-1)}{n}}=g_n^kganak=ganak(p−1)=gnk(p−1)=gnk通过观察不难得出:gnn≡gp−1≡1(modp)g_n^n \equiv g^{p-1} \equiv 1 (\mod p)gnn≡gp−1≡1(modp)又因为(gnn2)2≡1(modp)(g_n^{\frac{n}{2}})^2\equiv 1 (\mod p)(gn2n)2≡1(modp),且同属于一个简化剩余系,不能相等,
故gnn2≡−1(modp)g_n^{\frac{n}{2}}\equiv -1 (\mod p)gn2n≡−1(modp)同时我们有(gnk+n2)2≡(gnk)2×gnn≡(gnk)2(modp)(g_n^{k + \frac{n}{2}})^2\equiv (g_n^k)^2 \times g_n^n \equiv (g_n^{k})^2 (\mod p)(gnk+2n)2≡(gnk)2×gnn≡(gnk)2(modp)又因为同属于一个简化剩余系,不能相等,可得gnk+n2≡−gnk(modp)g_n^{k + \frac{n}{2}} \equiv -g_n^{k} (\mod p)gnk+2n≡−gnk(modp)
总而言之,单位根在FFT中用到的性质原根都有。
gnn≡1(modp)g_n^n \equiv 1 (\mod p)gnn≡1(modp)
ganak≡gnk(modp)g_{an}^{ak} \equiv g_n^k(\mod p)ganak≡gnk(modp)
gnn2≡−1(modp)g_n^{\frac{n}{2}}\equiv -1 (\mod p)gn2n≡−1(modp)
gnk+n2≡−gnk(modp)g_n^{k + \frac{n}{2}} \equiv -g_n^{k} (\mod p)gnk+2n≡−gnk(modp)
所以在DFT的过程中带入gnkg_n^kgnk和gnk+n2g_n^{k+\frac{n}{2}}gnk+2n实质上和带入ωnk\omega_n^kωnk和ωnk+n2\omega_n^{k+\frac{n}{2}}ωnk+2n一样。
IDFT
和FFT一样,用矩阵来辅助思考:
[gn0gn0gn0⋯gn0gn0gn1gn2⋯gnn−1gn0gn2gn4⋯gn2n−2gn0gn3gn6⋯gn3n−3⋮⋮⋮⋱⋮gn0gnn−1gn2n−2⋯gn(n−1)2]∗[a0a1a2a3⋮an−1]=[F0F1F2F3⋮Fn−1]\left[ \begin{array}{ccccc} g_{n}^{0} &g_{n}^{0}&g_{n}^{0} & \cdots & g_{n}^{0} \\ g_{n}^{0}&g_{n}^{1}&g_{n}^{2} & \cdots & g_{n}^{n-1} \\ g_{n}^{0} & g_{n}^{2} & g_{n}^{4} & \cdots &g_{n}^{2n-2} \\ g_{n}^{0} &g_{n}^{3} & g_{n}^{6} &\cdots & g_{n}^{3n-3} \\ \vdots & \vdots & \vdots &\ddots & \vdots \\ g_{n}^{0} &g_{n}^{n-1} &g_{n}^{2n-2} &\cdots &g_{n}^{(n-1)^2}\\ \end{array} \right] * \left[ \begin{array}{ccccc} a_0\\ a_1\\ a_2\\ a_3\\ \vdots\\ a_{n-1}\\ \end{array} \right] = \left[ \begin{array}{ccccc} F_0\\ F_1\\ F_2\\ F_3\\ \vdots\\ F_{n-1}\\ \end{array} \right] ⎣⎢⎢⎢⎢⎢⎢⎢⎡gn0gn0gn0gn0⋮gn0gn0gn1gn2gn3⋮gnn−1gn0gn2gn4gn6⋮gn2n−2⋯⋯⋯⋯⋱⋯gn0gnn−1gn2n−2gn3n−3⋮gn(n−1)2⎦⎥⎥⎥⎥⎥⎥⎥⎤∗⎣⎢⎢⎢⎢⎢⎢⎢⎡a0a1a2a3⋮an−1⎦⎥⎥⎥⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎢⎢⎢⎡F0F1F2F3⋮Fn−1⎦⎥⎥⎥⎥⎥⎥⎥⎤
和FFT不太一样,乘以单位根的共轭复数的过程将变成乘以原根在模ppp意义下的逆元,最后再乘以长度nnn模ppp下的逆元即可。
证明如下(以下运算均在模ppp的条件下进行):
gnk=g(p−1n)kg_n^k=g^{(\frac{p-1}{n})k}gnk=g(np−1)k
如上矩阵,F(k)=∑j=0n−1a(j)(gni)jkF(k)=\sum_{j=0}^{n-1}a(j) (g_n^{ i })^{jk}F(k)=j=0∑n−1a(j)(gni)jk
逆变换为:
1n∑k=0n−1F(k)(gni)−jk=1n∑k=0n−1(∑l=0n−1a(l)(gni)lk)(gni)−jk=1n∑k=0n−1(∑l=0n−1a(l)(gni)(l−j)k)\frac{1}{n} \sum_{k=0}^{n-1}F(k) (g_n^{ i })^{ -jk }=\frac{1}{n} \sum_{k=0}^{n-1}( \sum_{l=0}^{n-1}a(l)(g_n^{ i })^{ lk } )(g_n^{ i })^{- jk }=\frac{1}{n} \sum_{k=0}^{n-1}( \sum_{l=0}^{n-1}a(l)(g_n^{ i })^{ (l-j)k } )n1k=0∑n−1F(k)(gni)−jk=n1k=0∑n−1(l=0∑n−1a(l)(gni)lk)(gni)−jk=n1k=0∑n−1(l=0∑n−1a(l)(gni)(l−j)k)
式子=1n∑k=0n−1(a(0)(gni)(0−j)k+a(1)(gni)(1−j)k+⋯+a(n−1)(gni)((n−1)−j)k)=\frac{1}{n} \sum_{k=0}^{n-1}( a(0)(g_n^{ i })^{ (0-j)k} + a(1)(g_n^{ i })^{ (1-j)k} + \cdots + a(n-1)(g_n^{ i })^{ ((n - 1)-j)k} )=n1k=0∑n−1(a(0)(gni)(0−j)k+a(1)(gni)(1−j)k+⋯+a(n−1)(gni)((n−1)−j)k)
展开kkk的求和式得
=1n[a(0)((gni)(0−j)0+(gni)(0−j)1+⋯+(gni)(0−j)(n−1))+a(1)((gni)(1−j)0+(gni)(1−j)1+⋯+(gni)(1−j)(n−1))+⋯+a(n−1)((gni)((n−1)−j)0+(gni)((n−1)−j)1+⋯+(gni)((n−1)−j)(n−1))]=\frac{1}{n}[ a(0)((g_n^{ i })^{ (0-j)0 }+(g_n^{ i })^{ (0-j)1 } + \cdots + (g_n^{ i })^{ (0-j)(n-1) }) + \\ a(1)((g_n^{ i })^{ (1-j)0 }+(g_n^{ i })^{ (1-j)1 } + \cdots + (g_n^{ i })^{ (1-j)(n-1) }) + \\ \cdots + \\ a(n-1)((g_n^{ i })^{ ((n-1)-j)0 }+(g_n^{ i })^{ ((n-1)-j)1 } + \cdots + (g_n^{ i })^{ ((n-1)-j)(n-1) }) ]=n1[a(0)((gni)(0−j)0+(gni)(0−j)1+⋯+(gni)(0−j)(n−1))+a(1)((gni)(1−j)0+(gni)(1−j)1+⋯+(gni)(1−j)(n−1))+⋯+a(n−1)((gni)((n−1)−j)0+(gni)((n−1)−j)1+⋯+(gni)((n−1)−j)(n−1))]
由等比数列的求和公式得:
a(x)((gni)(x−j)0+(gni)(x−j)1+⋯+(gni)(x−j)(n−1))=a(x)1−((gni)n(x−j))n1−(gni)(x−j)a(x)((g_n^{ i })^{ (x-j)0 }+(g_n^{ i })^{ (x-j)1 } + \cdots + (g_n^{ i })^{ (x-j)(n-1) })=a(x) \frac{1- ((g_n^{ i })^{{n}(x-j)})^n}{1- (g_n^{ i })^{(x-j)} }a(x)((gni)(x−j)0+(gni)(x−j)1+⋯+(gni)(x−j)(n−1))=a(x)1−(gni)(x−j)1−((gni)n(x−j))n当j≠xj \neq xj=x,可得:a(x)1−((gni)(x−j))n1−(gni)(x−j)=a(x)1−(gi)(x−j)1−(gni)(x−j)=a(x)1−1(x−j)1−(gni)(x−j)=0a(x) \frac{1- ((g_n^{ i })^{(x-j)})^n}{1- (g_n^{ i })^{(x-j)} }=a(x) \frac{1- (g^{ i })^{(x-j)} }{1- (g_n^{ i })^{(x-j)} }=a(x) \frac{1- 1^{ (x-j)}}{1- (g_n^{ i })^{(x-j)} }=0a(x)1−(gni)(x−j)1−((gni)(x−j))n=a(x)1−(gni)(x−j)1−(gi)(x−j)=a(x)1−(gni)(x−j)1−1(x−j)=0
当j=xj = xj=x,可得:a(x)((gni)(x−j)0+(gni)(x−j)1+⋯+(gni)(x−j)(n−1))=na(x)a(x)((g_n^{ i })^{ (x-j)0 }+(g_n^{ i })^{ (x-j)1 } + \cdots + (g_n^{ i })^{ (x-j)(n-1) })=na(x)a(x)((gni)(x−j)0+(gni)(x−j)1+⋯+(gni)(x−j)(n−1))=na(x)
因而:1n∑k=0n−1F(k)(gni)−jk=1n∑k=0n−1(∑l=0n−1a(l)(gni)lk)(gni)−jk=na(x)\frac{1}{n} \sum_{k=0}^{n-1}F(k) (g_n^{ i })^{ -jk }=\frac{1}{n} \sum_{k=0}^{n-1}( \sum_{l=0}^{n-1}a(l)(g_n^{ i })^{ lk } )(g_n^{ i })^{- jk }=na(x)n1k=0∑n−1F(k)(gni)−jk=n1k=0∑n−1(l=0∑n−1a(l)(gni)lk)(gni)−jk=na(x)
该矩阵的逆矩阵得证。
模板题
贴上DFT与IDFT结合起来的代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1 << 22;
const int g = 3 , gi = 332748118 , mod = 998244353;//不加const时间翻四倍
ll qw( ll a , ll b ) {ll ans = 1;while ( b ) {if( b & 1 ) {ans = ans * a % mod;}a = a * a % mod;b >>= 1;}return ans;
}
int rev[N];
int n , m , l;
ll a[N] , b[N];
void pre( int bit ) {for ( int i = 0 ; i < ( 1 << bit ) ; ++i ) {rev[i] = (rev[i>>1]>>1)|((i&1)<<(bit - 1));}
}
void NTT( ll *F , int len , int on ) {for ( int i = 0 ; i < len ; ++i ) {//把递归的底层交换好if ( i < rev[i] ) {swap( F[i] , F[rev[i]] );}}for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走 ll gn = qw( on ? g : gi , ( mod - 1 ) / ( i ) );for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长 ll gg = 1;for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素ll u = F[k];ll t = gg * F[k + i / 2] % mod;F[k] = (u + t) % mod;F[k + i / 2] = ( u - t + mod ) % mod;gg = gg * gn % mod;}}}return;
}int main () {int len = 0;scanf("%d%d",&n,&m);for ( int i = 0 ; i <= n ; ++i ) {scanf("%lld",a + i);}for ( int i = 0 ; i <= m ; ++i ) {scanf("%lld",b + i);}len = n + m;int tim = 1;l = 0;while( tim <= len ) {tim <<= 1;l++;}len = tim;pre(l);NTT( a , len , 1 );NTT( b , len , 1 );for ( int i = 0 ; i <= len - 1 ; ++i ) {a[i] = a[i] * b[i] % mod;}NTT( a , len , 0 );ll inv = qw( len , mod - 2 );for ( int i = 0 ; i <= n + m ; ++i ) {printf("%lld ",a[i] * inv % mod);}return 0;
}
任意模数的坑以后再填吧QWQ。
多项式算法2:NTT(快速数论变换)相关推荐
- FFT 快速傅里叶变换 NTT 快速数论变换
SDNU 1531 a*b III (FFT模板) Description 计算a乘b,多组输入(50组以内). Input 输入a b,数据范围0 <= a,b <= 10^100000 ...
- 数学推导题,NTT,快速数论变换,Wannafly-乒乓球
乒乓球 题目描述 小 BoBoBo 是某省乒乓球名列前茅的选手,现在他有 nnn 颗乒乓球一字排开,第iii颗乒乓球的权值为 wiw_iwi 每次他会随机从现有的乒乓球中等概率选一颗拿走,然后得到的 ...
- 数学推导题,NTT,快速数论变换,Wannafly-导数卷积
导数卷积 题目描述 题解 参考了一下标程的推导过程,因为这个推导对我这种数学弱渣真的有点难鸭. [1]f(x)f(x)f(x)的iii次导函数: f(i)(x)=ai∗i!0!+ai+1∗(i+1)! ...
- A*B NTT快速数论变换
wmq的A×B Problem 发布时间: 2017年4月9日 17:06 最后更新: 2017年4月9日 17:07 时间限制: 3000ms 内存限制: 512M 描述 这是一个非常简 ...
- NTT(快速数论变换)模板
NTT好文 NTT快速数论变换,在acm中用于解决一类多项式卷积问题. 多项式A和多项式B求卷积后的多项式为C. 显然,如果A是n次多项式,B有m次多项式,这两个多项式都不缺项的话,乘积应为n+m次多 ...
- 算法学习FFT系列(2):快速数论变换NTT bzoj3992: [SDOI2015]序列统计例题详解
bzoj3992: [SDOI2015]序列统计 Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属 ...
- 【学习笔记】超简单的快速数论变换(NTT)(FFT的优化)(含全套证明)
整理的算法模板合集: ACM模板 目录 一.前置知识 二.快速数论变换(NTT) 三.NTT证明(和FFT的关系) 四.NTT模板 数组形式的实现 vector形式的实现 点我看多项式全家桶(●^◡_ ...
- 比FFT还容易明白的NTT(快速数论变换)
NTT相关 一种快速数论变换算法,这种算法是以数论为基础,对样本点为的数论变换,按时间抽取的方法,得到一组等价的迭代方程,有效高速简化了方程中的计算公式·与直接计算相比,大大减少了运算次数.(见快速傅 ...
- python蝴蝶代码_快速数论变换(NTT)及蝴蝶操作构造详解
快速数论变换 本文不会从头开始介绍NTT算法,所以需要先了解FFT:永远在你身后:快速傅里叶变换(FFT)求解多项式乘法zhuanlan.zhihu.com 除此之外,先简单的铺垫一些数论的概念同余 ...
最新文章
- 果园种树java_Java版淘金果园系统
- 如何在64位的windows平台上安装需要c编译的python扩展库
- js数组去重,合集等操作
- .net core实践系列之短信服务-架构优化
- 开箱即用的安全方案:MaxCompute数据安全方案介绍
- Maven学习(八)-----Maven依赖机制
- Maven(3)---Maven POM
- mybatis-plus使用 generator 代码生成器生成实体类支持Swagger2
- Leetcode每日一题:724.Find Pivot Index(寻找中心索引)
- 张敬富审计百度云资源_钟平逻辑英语资源百度云
- Unique Email Addresses
- nlp基础—10.结巴分词的应用及底层原理剖析
- Android 自定义View(三)
- maven profiles配置_nexus3搭建maven私服(完整版)
- 阿里云视频点播(上传视频)服务最新版本使用方法(解决部分依赖无法下载或不存在问题)
- kubernetes增加删除master节点操作
- chmod -R 777使用.
- 8-四平方和定理(拉格朗日定理)
- JZ2440:yaffs2 格式根文件系统制作
- Erphplogin Pro 连接QQ/微博/微信登录/弹窗登录 WordPress插件
热门文章
- 关于如何使用打码平台识别验证码
- Centos7 安装 ftp服务器 --失败了 妈蛋的
- python egg_python egg 简介
- VUE父组件向子组件传递数据
- python中wb什么意思,使用Python,“ wb”在此代码中是什么意思?
- 【洛谷5069】纵使日薄西山【set】【树状数组】
- 在线代码离线翻译Chrome插件一马v0.0.8 2018-10-31
- 贸然用string比较的后果
- 微信小程序实现直播间点赞飘心效果的示例代码
- Oracle database oracle12c 完全卸载 一键卸载