\(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. redis5.5官方文档

    https://www.cnblogs.com/zsl-find/articles/11780974.html 博客 https://redis.io/topics/cluster-tutorial ...

  2. 十七 Ajax&校验用户名功能

    Ajax: 即"Asynchronous JavaScript And XML", 异步JavaScript和XML , 是指一种创建的交互式页面应用的网页开发技术,它并不是新的技 ...

  3. python2学习------基础语法5(常用容器以及相关操作)

    1.list(列表) #生成数据list a=[x for x in range(10)]; #print a; #遍历list for i in a: pass; #print i; #追加元素 a ...

  4. 单片机ADC检测4-20mA电路,以及计算方法

    单片机ADC检测4-20mA电路,以及计算方法 转载:https://www.hongchangzidonghua.com/?id=24 1,手里有一个4-20mA输出的压力传感器,假设测量范围是0M ...

  5. Maven添加Tomcat插件实现热部署

    Maven热部署,顾名思义就是可以不影响项目在服务器中的运行情况,可以实现项目代码的更新,减少启动,编译时间,达到快速开发的目的,也不需要手动拷贝war包到远程项目,可以直接将项目以及war包部署到远 ...

  6. Java中遍历 Session 和 Request

    转: session的遍历: java.util.Enumeration e = request.getSession().getAttributeNames(); while( e.hasMoreE ...

  7. 清北学堂例题 LUOGU2519 【HAOI2011】PROBLEM A

    题目描述 一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低.”问最少有几个人没有说真话(可能有相同的分数) 输入格式 第一行一个整数n,接下来n行每行两个整数,第i+1行 ...

  8. 017.CI4框架CodeIgniter数据库操作之:Updata更新修改一条数据

    01. 在Model中写入数据库操作的代码如下: <?php namespace App\Models\System; use CodeIgniter\Model; class User_mod ...

  9. System.Data.SqlClient.SqlException: 'Incorrect syntax near 'OFFSET'.

    https://www.nopcommerce.com/boards/t/54586/410-not-running-on-local-system.aspx#209684 Hello, I was ...

  10. 使用 mtd-utils 烧写Arm Linux 系统各个部分

    有关博客:<Arm-Linux 移植 mtd-utils 1.x>.<mtd-utils 的 使用> 背景: 作为一项技术储备,可用于增强系统可维护性. 要求: 要求主板以mt ...