\(NTT\),快速数论变换,可以理解为带模数的FFT


原根 & 阶

先来补一点数论。(这里讲的应该很少,都是针对\(ntt\)胡的,具体的话可以去看《初等数论》那本小黄书)。

阶(指数)

如果\(m > 1, (a,m) = 1\),那么必有整数\(d\),使得下面这个柿子成立

\[a^d \equiv 1 \ (mod \ m)
\]

我们称令这个柿子成立的最小的\(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\)的时候我们用到了单位根的几个性质

    1. \(\omega_{n}^{k} = (\omega_{n}^{1})^k\)
    1. \(\omega_{n}^{k} = \omega_{n}^{k \ mod \ n}\)
    1. \(\omega_{n}^{k + n/2} = -\omega_{n}^{k}\)
    1. \(\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}\),我们不用证)。

    1. \(\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\),代上去即证。

    1. \(\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)\)。代上去就好了。

    1. \(\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$的更多相关文章

  1. Linux特殊符号浅谈

    Linux特殊字符浅谈 我们经常跟键盘上面那些特殊符号比如(?.!.~...)打交道,其实在Linux有其独特的含义,大致可以分为三类:Linux特殊符号.通配符.正则表达式. Linux特殊符号又可 ...

  2. Spring缓存框架原理浅谈

    运维在上线,无聊写博客.最近看了下Spring的缓存框架,这里写一下 1.Spring 缓存框架 原理浅谈 2.Spring 缓存框架 注解使用说明 3.Spring 缓存配置 + Ehcache(默 ...

  3. android assets文件夹浅谈

    ---恢复内容开始--- 最近在研究assets文件夹的一些属性跟使用方法.根据网上一些文章.实例做一下汇总,拿出来跟大家分享下,有不足的地方还请多多指教. 首先了解一下assets是干什么用的,as ...

  4. jsp内置对象浅谈

    jsp内置对象浅谈 | 浏览:1184 | 更新:2013-12-11 16:01 JSP内置对象:我们在使用JSP进行页面编程时可以直接使用而不需自己创建的一些Web容器已为用户创建好的JSP内置对 ...

  5. Android性能优化的浅谈

    一.概要: 本文主要以Android的渲染机制.UI优化.多线程的处理.缓存处理.电量优化以及代码规范等几方面来简述Android的性能优化 二.渲染机制的优化: 大多数用户感知到的卡顿等性能问题的最 ...

  6. .net中对象序列化技术浅谈

    .net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储 ...

  7. javascript数组浅谈2

    上次说了数组元素的增删,的这次说说数组的一些操作方法 join()方法: ,,] arr.join("_") //1_2_3 join方法会返回一个由数组中每个值的字符串形式拼接而 ...

  8. javascript数组浅谈1

    最近心血来潮要开始玩博客了,刚好也在看数组这块内容,第一篇就只好拿数组开刀了,自己总结的,有什么不对的地方还请批评指正,还有什么没写到的方面也可以提出来我进行完善,谢谢~~ 首先,大概说说数组的基本用 ...

  9. JavaScript中toStirng()与Object.prototype.toString.call()方法浅谈

    toStirng()与Object.prototype.toString.call()方法浅谈 一.toString()是一个怎样的方法?它是能将某一个值转化为字符串的方法.然而它是如何将一个值从一种 ...

  10. 【转】Android Canvas的save(),saveLayer()和restore()浅谈

    Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22      阅读:1445      评论:0      收藏: ...

随机推荐

  1. vld扩展

    PHP代码的执行实际上是在执行代码解析后的各种opcode.通过vld扩展可以很方便地看到执行过程中的opcode. 一.安装vld扩展 git clone https://github.com/de ...

  2. C++ 动态多态

    背景 以前的学习,只是简单地知道:面向对象的三大特性(封装.继承.多态) ,在项目开发中,用到了多态而自己却不知道. 多态(Polymorphism)按字面的意思就是"多种状态". ...

  3. Golang:GOROOT、GOPATH、GOBIN变量的含义

    背景 移植完了go以后,在配置变量的时候不太清楚这些变量的含义,找了有关的资料. 使用 go env可以获取 go 有关的环境变量,下面是我的go环境: GO111MODULE="" ...

  4. Ubuntu系统部署tomcat并启用JMX实战案例

    Ubuntu系统部署tomcat并启用JMX实战案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.安装JDK环境 1>.更换阿里云的软件源 [root@zabbix_g ...

  5. Google宣布安全奖励项目(GPSRP)覆盖上亿的 Android 应用程序

    自 2010 年推出除虫赏金项目以来,谷歌已经向安全研究人员支付了超过 1500 万美元的奖励.今天,这家科技巨头宣布进一步拓展 Google Play 安全奖励项目(GPSRP)的范围,以覆盖上亿的 ...

  6. C++面试常见问题——17类模板的使用

    类模板的使用 注意在每次类模板函数时都需要声明一个类模板 #include<iostream> using namespace std; template <class T,int ...

  7. Docker 学习之部署php + nginx(一)

    博主电脑系统是window 10 专业版的,所以在此记录下docker的基本使用方法. 参考地址: https://www.runoob.com/docker/docker-install-php.h ...

  8. Java 布尔运算

    章节 Java 基础 Java 简介 Java 环境搭建 Java 基本语法 Java 注释 Java 变量 Java 数据类型 Java 字符串 Java 类型转换 Java 运算符 Java 字符 ...

  9. JuJu团队11月25号工作汇报

    JuJu团队11月25号工作汇报 JuJu   Scrum 团队成员 今日工作 剩余任务 困难 于达 实现随机采样函数,进行onehot处理 预处理数据集,将数据集转为矩阵读入 数据集预处理比想象中麻 ...

  10. thinkphp配置到二级目录,不配置到根目录,访问除首页的其他路径都是404报错

    1.在nginx的配置里面,进行重定向 vi /etc/nginx/conf.d/default.conf 2.进入编辑 location /thinkphp/public { if (!-e $re ...