\(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. 二 配置数据字典&异步查询客户

    数据字典: 字典表和客户表的关系 配置字典表 配置客户表 Spring管理映射文件 1 字典表和客户表的关系 2 配置字典表 3  配置客户表 4  Spring管理映射文件 异步查询客户: 页面加载 ...

  2. 十九 Listener

    Listener 监听器 一 监听器内部原理:其实就是接口回调 需求:A在执行循环,当循环到5的时候,通知B 事先先把某一个对象传递给A ,当A执行到5的时候,通过这个对象来调用B中的方法 但是不是直 ...

  3. 题解 nflsoj489 【六校联合训练 CSP #15】小D与随机

    题目链接 考虑枚举好点的集合.此时要考虑的问题是如何填入\(1\sim n\)这些数使得恰好我们枚举到的这些点是好点,即:求出有多少种合法的填数方案. \(1\)号点一定是好点.那么除\(1\)号点外 ...

  4. CentOS操作系统部署zabbix agent服务

    CentOS操作系统部署zabbix agent服务 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.查看zabbix的官方手册 1>.点击下载 2>.查看Ubunt ...

  5. 洛谷 P5509 派遣

    题目传送门 心路历程: 每想到一种思路,就有一种要做出来的感觉.但一接着想就会发现这种方法有一些极小的问题,但是我没法解决... 于是就再换思路... 最后在请教了出题人神仙zcq之后,终于做出来了 ...

  6. 013.CI4框架CodeIgniter数据库操作之:查询数据库,并让数据以数组的方式返回查询结果

    01. 我们在CI4框架中的Model文件夹新建一个User_model.php的文件,使用的是getResultArray,表示并让数据以数组的方式返回查询结果,代码如下: <?php nam ...

  7. SmartAssembly .net混淆后,无法找到部分类型

    两种解决方式: 1,在vs项目引用中,COM  嵌入互操作类型, 全部设为false. 2, 在混淆选项中,排除所有 引有的 外部COM类

  8. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 字体图标(Glyphicons):glyphicon glyphicon-stop

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name ...

  9. POJ 2823 滑动窗口 单调队列模板

    我们从最简单的问题开始: 给定一个长度为N的整数数列a(i),i=0,1,...,N-1和窗长度k. 要求: f(i) = max{a(i-k+1),a(i-k+2),..., a(i)},i = 0 ...

  10. C语言备忘录——qsort

    写了这么久的排序感觉还是用现成的最舒服.其实C语言其实自己带了一个快速排序在stdlib 库里,但是其函数调用的接口过于复杂,所以让人望而却步.为了加深自己的记忆,所以写下这篇博客 先来看一下函数原型 ...