浅谈$NTT$
\(NTT\),快速数论变换,可以理解为带模数的FFT。
原根 & 阶
先来补一点数论。(这里讲的应该很少,都是针对\(ntt\)胡的,具体的话可以去看《初等数论》那本小黄书)。
阶(指数)
如果\(m > 1, (a,m) = 1\),那么必有整数\(d\),使得下面这个柿子成立
\]
我们称令这个柿子成立的最小的\(d\)叫\(a \ mod \ m\)的 阶(或指数),记作\(\delta_m(a)\)。
一些性质
\((1).\) 因为\(a,m\)互质,所以根据欧拉定理\(a^{\varphi(m)} \equiv 1 \ (mod \ m)\),立即得到\(\delta_m(a) \le \varphi(m)\)
$(2). $ 若\(d_0\)也满足\(a^{d_0} \equiv 1 \ (mod \ m)\),那么必有\(\delta_m(a) \mid d_0\),这个显然。
$(3). $ 由\((1)\)和\((2)\)可以立即推出\(\delta_m(a) \mid \varphi(m)\)
\(\cdots\)
原根
若\(m > 1, (a,m) = 1\),且\(\delta_m(a) = \varphi(m)\),那么则称\(a\)是\(mod \ m\)的一个原根。
特别\(m\)是质数\(p\)的时候若\(\delta_p(a) = p-1\),则\(a\)是原根。
性质
设\(g\)是\(m\)的原根,那么\(g, g^2,g^3,\cdots,g^{\varphi(m)}\)一定是在\(mod \ m\)意义下与\(m\)互质的\(\varphi(m)\)个数的一个排列。这个根据定义也显然。
特别的当\(m\)是一个质数\(p\)的时候,有\(g, g^2, g^3, \cdots, g^{p-1}\)在\(mod \ p\)意义下是\(1...p-1\)这\(p-1\)个数的一个排列。
偷偷告诉你,原根一般都很小,默认<=100就好了
那对于一个素数,如何找原根呢?
直接暴力,枚举\(g = 1...100\),然后\(check\),根据阶性质\((1)\)我们在枚举\(d\)的时候可以枚举\(p-1\)的因子,注意不要枚举到\(p-1\)。
#include <bits/stdc++.h>
using namespace std;
int p;
int fpow(int a,int b,int mod=p){
int ret=1; for (;b;b>>=1,a=1ll*a*a%mod) if (b&1) ret=1ll*ret*a%mod;
return ret;
}
int chk(int g){
for (int i=2;i*i<=p-1;i++) // !!从2开始
if ((p-1)%i==0&&(fpow(g,i)==1||fpow(g,(p-1)/i)==1)) return 0;
return 1;
}
int main(){
scanf("%d",&p);
for (int i=1;i<=100;i++)
if (chk(i)){printf("G: %d\n",i); return 0;}
return 1;
return 0;
}
安利一个巨佬原根表
NTT
P.S. 以下的原根都是在一个形如\(k2^t + 1\)的质数\(p\)的同余系下定义的。(\(t\)足够大,一般\(t \ge 20\))
设\(g\)是\(mod \ p\)的原根,那么\(g, g^2, \cdots, g^{p-1}\)在\(mod \ p\)意义下是\(p-1\)个不同的数。
回去看一下FFT,随便交一发就\(T\)了,你看这个FFT,他就是逊啦!。
发现\(FFT\)还有很多不足之处
复数运算,浮点数乘法,自带无穷大常数
多次调用三角函数,常数大,还掉精度
复数乘法异常难背,还要手算一遍
\(\cdots\)
总之我们得想办法把频繁的复数运算和单位根去掉。
在颓\(FFT\)的时候我们用到了单位根的几个性质
- \(\omega_{n}^{k} = (\omega_{n}^{1})^k\)
- \(\omega_{n}^{k} = \omega_{n}^{k \ mod \ n}\)
- \(\omega_{n}^{k + n/2} = -\omega_{n}^{k}\)
- \(\omega_{2n}^{2k} = \omega_{n}^{k}\)
当然根据单位根的定义,\(\omega_{n}^{0}, \omega_{n}^{1}, \cdots, \omega_{n}^{n-1}\)必须是\(n-1\)个不同的数。
结论就是我们在\(mod \ p\)意义下,我们用原根\(g\)的\((p-1)/n\)次方代替\(\omega_{n}^{1}\)就对了,即\(\omega_{n}^1 = g^{(p-1)/n}\)。
我们来证一下性质(一下运算均是在\(mod \ p\)意义下的)。(1. 就是定义,即\(\omega_{n}^{k} = (\omega_{n}^1)^k = g^{k(p-1)/n}\),我们不用证)。
\(\omega_{n}^{k} = \omega_{n}^{k \% n}\)
因为\(p\)是质数,有小费马\(a^{p-1} \equiv 1 \ (mod \ p)\),立刻得到\(g^{(p-1)k/n} = g^{(p-1)k/n \% (p-1)}\)
这时候并不能直接取模,因为\(k/n\)不一定是整数。我们令\(\{ x \}\)表示\(x\)的小数部分。
有\((p-1)k/n \% (p-1) = (p-1)\{ k/n \} = (p-1)(k \% n) /n\),代上去即证。
\(\omega_{n}^{k+n/2} = -\omega_{n}^{k}\)
根据定义,有\(\omega_{n}^{k+n/2} = \omega_{n}^{k} \cdot \omega_{n}^{n/2}\)
由于\((\omega_{n}^{n/2})^2 = \omega_{n}^{n} = 1\),所以\(\omega_{n}^{n/2}\)在\(mod \ p\)意义下只可能是\(1\)或\(-1\),又因为\(\omega_{n}^{n/2} \not= \omega_{n}^{0}\),所以\(\omega_{n}^{n/2} \equiv -1 ( mod \ p)\)。代上去就好了。
\(\omega_{2n}^{2k} = \omega_{n}^{k}\)
这个应该是最好证的。。。
把\(g^{(p-1)/n}\)代上去,\(g^{(p-1)2k/2n} = g^{(p-1)k/n}\)。没了。
综上所述,在\(mod \ p\)意义下用\(g^{(p-1)/n}\)代替\(\omega_{n}^{1}\)一点毛病都没有,在\(IDFT\)的时候用\((\omega_{n}^{1})^{-1}\)代替\(\omega_{n}^{-1}\),算逆元就好了。
所以再来看一下板子题 才发现这里系数都<10
我们取一个信仰膜数\(p = 998244353\),然后在\(mod \ p\)意义下做\(NTT\),没了。
#include <bits/stdc++.h>
using namespace std;
inline int read(){
char ch=getchar();int x=0,f=1;
while (!isdigit(ch)){f=ch=='-'?-f:f;ch=getchar();}
while (isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=4e6+10,P=998244353,G=3,IG=332748118;
int fpow(int a,int b,int mod=P){
int ret=1; for (;b;b>>=1,a=1ll*a*a%P) if (b&1) ret=1ll*a*ret%P;
return ret;
}
#define add(x,y) ((x)+(y)>=P?(x)+(y)-P:(x)+(y))
#define sub(x,y) ((x)-(y)<0?(x)-(y)+P:(x)-(y))
int rev[N];
void ntt(int *f,int n,int flg){
for (int i=0;i<n;i++) if (i<rev[i]) swap(f[i],f[rev[i]]);
for (int len=2;len<=n;len<<=1){
int w1=fpow(flg==1?G:IG,(P-1)/len); // w^{-1} = (w^1)^{-1}
for (int i=0;i<n;i+=len){
int w=1;
for (int j=i;j<(len>>1)+i;j++){
int tmp=1ll*w*f[j+(len>>1)]%P;
f[j+(len>>1)]=sub(f[j],tmp);
f[j]=add(f[j],tmp);
w=1ll*w*w1%P;
}
}
}
}
int f[N],g[N];
int main(){
int n=read(),m=read();
for (int i=0;i<=n;i++) f[i]=read();
for (int i=0;i<=m;i++) g[i]=read();
int limit=1; while (limit<=n+m)limit<<=1;
for (int i=0;i<limit;i++) rev[i]=rev[i>>1]>>1|((i&1)?limit>>1:0);
ntt(f,limit,1),ntt(g,limit,1);
for (int i=0;i<limit;i++) f[i]=1ll*f[i]*g[i]%P;
ntt(f,limit,-1); int ivn=fpow(limit,P-2);
for (int i=0;i<=m+n;i++) printf("%d ",1ll*f[i]*ivn%P);
return 0;
}
提交记录,总共\(1.7s\),nice!
浅谈$NTT$的更多相关文章
- Linux特殊符号浅谈
Linux特殊字符浅谈 我们经常跟键盘上面那些特殊符号比如(?.!.~...)打交道,其实在Linux有其独特的含义,大致可以分为三类:Linux特殊符号.通配符.正则表达式. Linux特殊符号又可 ...
- Spring缓存框架原理浅谈
运维在上线,无聊写博客.最近看了下Spring的缓存框架,这里写一下 1.Spring 缓存框架 原理浅谈 2.Spring 缓存框架 注解使用说明 3.Spring 缓存配置 + Ehcache(默 ...
- android assets文件夹浅谈
---恢复内容开始--- 最近在研究assets文件夹的一些属性跟使用方法.根据网上一些文章.实例做一下汇总,拿出来跟大家分享下,有不足的地方还请多多指教. 首先了解一下assets是干什么用的,as ...
- jsp内置对象浅谈
jsp内置对象浅谈 | 浏览:1184 | 更新:2013-12-11 16:01 JSP内置对象:我们在使用JSP进行页面编程时可以直接使用而不需自己创建的一些Web容器已为用户创建好的JSP内置对 ...
- Android性能优化的浅谈
一.概要: 本文主要以Android的渲染机制.UI优化.多线程的处理.缓存处理.电量优化以及代码规范等几方面来简述Android的性能优化 二.渲染机制的优化: 大多数用户感知到的卡顿等性能问题的最 ...
- .net中对象序列化技术浅谈
.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储 ...
- javascript数组浅谈2
上次说了数组元素的增删,的这次说说数组的一些操作方法 join()方法: ,,] arr.join("_") //1_2_3 join方法会返回一个由数组中每个值的字符串形式拼接而 ...
- javascript数组浅谈1
最近心血来潮要开始玩博客了,刚好也在看数组这块内容,第一篇就只好拿数组开刀了,自己总结的,有什么不对的地方还请批评指正,还有什么没写到的方面也可以提出来我进行完善,谢谢~~ 首先,大概说说数组的基本用 ...
- JavaScript中toStirng()与Object.prototype.toString.call()方法浅谈
toStirng()与Object.prototype.toString.call()方法浅谈 一.toString()是一个怎样的方法?它是能将某一个值转化为字符串的方法.然而它是如何将一个值从一种 ...
- 【转】Android Canvas的save(),saveLayer()和restore()浅谈
Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22 阅读:1445 评论:0 收藏: ...
随机推荐
- vld扩展
PHP代码的执行实际上是在执行代码解析后的各种opcode.通过vld扩展可以很方便地看到执行过程中的opcode. 一.安装vld扩展 git clone https://github.com/de ...
- C++ 动态多态
背景 以前的学习,只是简单地知道:面向对象的三大特性(封装.继承.多态) ,在项目开发中,用到了多态而自己却不知道. 多态(Polymorphism)按字面的意思就是"多种状态". ...
- Golang:GOROOT、GOPATH、GOBIN变量的含义
背景 移植完了go以后,在配置变量的时候不太清楚这些变量的含义,找了有关的资料. 使用 go env可以获取 go 有关的环境变量,下面是我的go环境: GO111MODULE="" ...
- Ubuntu系统部署tomcat并启用JMX实战案例
Ubuntu系统部署tomcat并启用JMX实战案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.安装JDK环境 1>.更换阿里云的软件源 [root@zabbix_g ...
- Google宣布安全奖励项目(GPSRP)覆盖上亿的 Android 应用程序
自 2010 年推出除虫赏金项目以来,谷歌已经向安全研究人员支付了超过 1500 万美元的奖励.今天,这家科技巨头宣布进一步拓展 Google Play 安全奖励项目(GPSRP)的范围,以覆盖上亿的 ...
- C++面试常见问题——17类模板的使用
类模板的使用 注意在每次类模板函数时都需要声明一个类模板 #include<iostream> using namespace std; template <class T,int ...
- Docker 学习之部署php + nginx(一)
博主电脑系统是window 10 专业版的,所以在此记录下docker的基本使用方法. 参考地址: https://www.runoob.com/docker/docker-install-php.h ...
- Java 布尔运算
章节 Java 基础 Java 简介 Java 环境搭建 Java 基本语法 Java 注释 Java 变量 Java 数据类型 Java 字符串 Java 类型转换 Java 运算符 Java 字符 ...
- JuJu团队11月25号工作汇报
JuJu团队11月25号工作汇报 JuJu Scrum 团队成员 今日工作 剩余任务 困难 于达 实现随机采样函数,进行onehot处理 预处理数据集,将数据集转为矩阵读入 数据集预处理比想象中麻 ...
- thinkphp配置到二级目录,不配置到根目录,访问除首页的其他路径都是404报错
1.在nginx的配置里面,进行重定向 vi /etc/nginx/conf.d/default.conf 2.进入编辑 location /thinkphp/public { if (!-e $re ...