\(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\)。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int p;
  4. int fpow(int a,int b,int mod=p){
  5. int ret=1; for (;b;b>>=1,a=1ll*a*a%mod) if (b&1) ret=1ll*ret*a%mod;
  6. return ret;
  7. }
  8. int chk(int g){
  9. for (int i=2;i*i<=p-1;i++) // !!从2开始
  10. if ((p-1)%i==0&&(fpow(g,i)==1||fpow(g,(p-1)/i)==1)) return 0;
  11. return 1;
  12. }
  13. int main(){
  14. scanf("%d",&p);
  15. for (int i=1;i<=100;i++)
  16. if (chk(i)){printf("G: %d\n",i); return 0;}
  17. return 1;
  18. return 0;
  19. }

安利一个巨佬原根表


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\),没了。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. inline int read(){
  4. char ch=getchar();int x=0,f=1;
  5. while (!isdigit(ch)){f=ch=='-'?-f:f;ch=getchar();}
  6. while (isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
  7. return x*f;
  8. }
  9. const int N=4e6+10,P=998244353,G=3,IG=332748118;
  10. int fpow(int a,int b,int mod=P){
  11. int ret=1; for (;b;b>>=1,a=1ll*a*a%P) if (b&1) ret=1ll*a*ret%P;
  12. return ret;
  13. }
  14. #define add(x,y) ((x)+(y)>=P?(x)+(y)-P:(x)+(y))
  15. #define sub(x,y) ((x)-(y)<0?(x)-(y)+P:(x)-(y))
  16. int rev[N];
  17. void ntt(int *f,int n,int flg){
  18. for (int i=0;i<n;i++) if (i<rev[i]) swap(f[i],f[rev[i]]);
  19. for (int len=2;len<=n;len<<=1){
  20. int w1=fpow(flg==1?G:IG,(P-1)/len); // w^{-1} = (w^1)^{-1}
  21. for (int i=0;i<n;i+=len){
  22. int w=1;
  23. for (int j=i;j<(len>>1)+i;j++){
  24. int tmp=1ll*w*f[j+(len>>1)]%P;
  25. f[j+(len>>1)]=sub(f[j],tmp);
  26. f[j]=add(f[j],tmp);
  27. w=1ll*w*w1%P;
  28. }
  29. }
  30. }
  31. }
  32. int f[N],g[N];
  33. int main(){
  34. int n=read(),m=read();
  35. for (int i=0;i<=n;i++) f[i]=read();
  36. for (int i=0;i<=m;i++) g[i]=read();
  37. int limit=1; while (limit<=n+m)limit<<=1;
  38. for (int i=0;i<limit;i++) rev[i]=rev[i>>1]>>1|((i&1)?limit>>1:0);
  39. ntt(f,limit,1),ntt(g,limit,1);
  40. for (int i=0;i<limit;i++) f[i]=1ll*f[i]*g[i]%P;
  41. ntt(f,limit,-1); int ivn=fpow(limit,P-2);
  42. for (int i=0;i<=m+n;i++) printf("%d ",1ll*f[i]*ivn%P);
  43. return 0;
  44. }

提交记录,总共\(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. Lesson 45 Of men and galaxies

    In man's early days, competition with other creatures must have been critical. But this phase of our ...

  2. 【Unity】鼠标指向某物体,在其上显示物体的名字等等等等信息

    之前一直用NGUI HUD Text插件做这个功能,感觉一个小功能就导一个插件进来简直丧心病狂.然后就自己写了一个~ Camera cam;//用于发射射线的相机 Camera UIcam;//UI层 ...

  3. 再次实践 MySQL chart【转】

    学习了 chart 结构和模板的知识后,现在重新实践一次 MySQL chart,相信会有更多收获. chart 安装前的准备 作为准备工作,安装之前需要先清楚 chart 的使用方法.这些信息通常记 ...

  4. delphi对ZIP解压

    Delphi 对GZIP解压 作者:admin 来源:未知 日期:2010/5/9 13:08:46 人气:获取失败 标签: QQ空间新浪微博腾讯微博腾讯朋友QQ收藏百度空间百度贴吧更多0 呵呵,终于 ...

  5. Easy_Re

    这题比较简单,一波常规的操作之后直接上ida(小白的常规操作在以前的博客里都有所以这里不在赘述了),ida打开之后查看一下, 这里应该就是一个入口点了,接着搜索flag字符串, 上面的黄色的部分转换成 ...

  6. @Autowired 和 @ Resource 的区别

    转 都是用来装配Bean的注解.都可以写在字段上,或写在setter方法上. @Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以 ...

  7. 061、Java中利用return结束方法调用

    01.代码如下: package TIANPAN; /** * 此处为文档注释 * * @author 田攀 微信382477247 */ public class TestDemo { public ...

  8. gem5-gpu 运行 PARSEC2.1

    PARSEC是针对共享内存多核处理器(CPU)的一套基准测试程序,详细介绍见wiki:http://wiki.cs.princeton.edu/index.php/PARSEC,主要参考:http:/ ...

  9. pyhton matplotlib可视化图像基础(二维函数图、柱状图、饼图、直方图以及折线图)

    //2019.07.22pyhton中matplotlib模块的应用pyhton中matplotlib是可视化图像库的第三方库,它可以实现图像的可视化,输出不同形式的图形1.可视化图形的输出和展示需要 ...

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

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