快速数论变换(NTT)

转自ACdreamers (http://blog.csdn.net/acdreamers/article/details/39026505

在上一篇文章中 http://blog.csdn.net/acdreamers/article/details/39005227 介绍了用快速傅里叶变

换来求多项式的乘法。可以发现它是利用了单位复根的特殊性质,大大减少了运算,但是这种做法是对复数系数的矩阵

加以处理,每个复数系数的实部和虚部是一个正弦及余弦函数,因此大部分系数都是浮点数,我们必须做复数及浮点数

的计算,计算量会比较大,而且浮点数的计算可能会导致误差增大。

今天,我将来介绍另一种计算多项式乘法的算法,叫做快速数论变换(NTT),在离散正交变换的理论中,已经证明在

复数域内,具有循环卷积特性的唯一变换是DFT,所以在复数域中不存在具有循环卷积性质的更简单的离散正交变换。

因此提出了以数论为基础的具有循环卷积性质的快速数论变换

回忆复数向量,其离散傅里叶变换公式如下

离散傅里叶逆变换公式为

今天的快速数论变换(NTT)是在上进行的,在快速傅里叶变换(FFT)中,通过次单位复根来运算的,即满

,而对于快速数论变换来说,则是可以将看成是的等价,这里是模素数

的原根(由于是素数,那么原根一定存在)。即

所以综上,我们得到数论变换的公式如下

数论变换的逆变换公式为

这样就把复数对应到一个整数,之后一切都是在系统内考虑。

上述数论变换(NTT)公式中,要求是素数且必须是的因子。由于经常是2的方幂,所以可以构造形

的素数。通常来说可以选择费马素数,这样的变换叫做费马数数论变换

这里我们选择,这样得到模的原根值为

题目:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1028

分析:题目意思就是大数相乘,此处用快速数论变换(NTT)实现。

  1 #include <iostream>
  2 #include <string.h>
  3 #include <stdio.h>
  4
  5 using namespace std;
  6 typedef long long LL;
  7
  8 const int N = 1 << 18;
  9 const int P = (479 << 21) + 1;
 10 const int G = 3;
 11 const int NUM = 20;
 12
 13 LL  wn[NUM];
 14 LL  a[N], b[N];
 15 char A[N], B[N];
 16
 17 LL quick_mod(LL a, LL b, LL m)
 18 {
 19     LL ans = 1;
 20     a %= m;
 21     while(b)
 22     {
 23         if(b & 1)
 24         {
 25             ans = ans * a % m;
 26             b--;
 27         }
 28         b >>= 1;
 29         a = a * a % m;
 30     }
 31     return ans;
 32 }
 33
 34 void GetWn()
 35 {
 36     for(int i=0; i<NUM; i++)
 37     {
 38         int t = 1 << i;
 39         wn[i] = quick_mod(G, (P - 1) / t, P);
 40     }
 41 }
 42
 43 void Prepare(char A[], char B[], LL a[], LL b[], int &len)
 44 {
 45     len = 1;
 46     int len_A = strlen(A);
 47     int len_B = strlen(B);
 48     while(len <= 2 * len_A || len <= 2 * len_B) len <<= 1;
 49     for(int i=0; i<len_A; i++)
 50         A[len - 1 - i] = A[len_A - 1 - i];
 51     for(int i=0; i<len - len_A; i++)
 52         A[i] = ‘0‘;
 53     for(int i=0; i<len_B; i++)
 54         B[len - 1 - i] = B[len_B - 1 - i];
 55     for(int i=0; i<len - len_B; i++)
 56         B[i] = ‘0‘;
 57     for(int i=0; i<len; i++)
 58         a[len - 1 - i] = A[i] - ‘0‘;
 59     for(int i=0; i<len; i++)
 60         b[len - 1 - i] = B[i] - ‘0‘;
 61 }
 62
 63 void Rader(LL a[], int len)
 64 {
 65     int j = len >> 1;
 66     for(int i=1; i<len-1; i++)
 67     {
 68         if(i < j) swap(a[i], a[j]);
 69         int k = len >> 1;
 70         while(j >= k)
 71         {
 72             j -= k;
 73             k >>= 1;
 74         }
 75         if(j < k) j += k;
 76     }
 77 }
 78
 79 void NTT(LL a[], int len, int on)
 80 {
 81     Rader(a, len);
 82     int id = 0;
 83     for(int h = 2; h <= len; h <<= 1)
 84     {
 85         id++;
 86         for(int j = 0; j < len; j += h)
 87         {
 88             LL w = 1;
 89             for(int k = j; k < j + h / 2; k++)
 90             {
 91                 LL u = a[k] % P;
 92                 LL t = w * (a[k + h / 2] % P) % P;
 93                 a[k] = (u + t) % P;
 94                 a[k + h / 2] = ((u - t) % P + P) % P;
 95                 w = w * wn[id] % P;
 96             }
 97         }
 98     }
 99     if(on == -1)
100     {
101         for(int i = 1; i < len / 2; i++)
102             swap(a[i], a[len - i]);
103         LL Inv = quick_mod(len, P - 2, P);
104         for(int i = 0; i < len; i++)
105             a[i] = a[i] % P * Inv % P;
106     }
107 }
108
109 void Conv(LL a[], LL b[], int n)
110 {
111     NTT(a, n, 1);
112     NTT(b, n, 1);
113     for(int i = 0; i < n; i++)
114         a[i] = a[i] * b[i] % P;
115     NTT(a, n, -1);
116 }
117
118 void Transfer(LL a[], int n)
119 {
120     int t = 0;
121     for(int i = 0; i < n; i++)
122     {
123         a[i] += t;
124         if(a[i] > 9)
125         {
126             t = a[i] / 10;
127             a[i] %= 10;
128         }
129         else t = 0;
130     }
131 }
132
133 void Print(LL a[], int n)
134 {
135     bool flag = 1;
136     for(int i = n - 1; i >= 0; i--)
137     {
138         if(a[i] != 0 && flag)
139         {
140             printf("%d", a[i]);
141             flag = 0;
142         }
143         else if(!flag)
144             printf("%d", a[i]);
145     }
146     puts("");
147 }
148
149 int main()
150 {
151     GetWn();
152     while(scanf("%s%s", A, B)!=EOF)
153     {
154         int len;
155         Prepare(A, B, a, b, len);
156         Conv(a, b, len);
157         Transfer(a, len);
158         Print(a, len);
159     }
160     return 0;
161 }  
时间: 2024-08-08 00:13:43

快速数论变换(NTT)的相关文章

快速数论变换(NTT)

今天的A题,裸的ntt,但我不会,于是白送了50分. 于是跑来学一下ntt. 题面很简单,就懒得贴了,那不是我要说的重点. 重点是NTT,也称快速数论变换. 在很多问题中,我们可能会遇到在模意义下的多项式乘法问题,这时传统的快速傅里叶变换可能就无法满足要求,这时候快速数论变换就派上了用场. 考虑快速傅里叶变换的实现,利用单位复根的特殊性质来减少运算,而利用的,就是dft变换的循环卷积特性.于是考虑在模意义下同样具有循环卷积特性的东西. 考虑在模p意义下(p为特定的质数,满足p=c?2n+1) 我

快速数论变换NTT模板

51nod 1348 乘积之和 #include <cmath> #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <bitset> #include <set>

快速数论变换模板(NTT)

快速数论变化(NTT)是的原理其实和快速傅里叶变换是一样的原理. 对于形如m= c*2^n+1的费马素数,假设其原根为g.那么瞒住g^(m-1)==1  而且正好(m-1)能整除2^n的.所所以可以在模p域进行NTT变换.旋转因子为  g^((m-1)/n).其他的原理都和FFT的原理相同.这样可以解决特殊情况下FFT的浮点误差. /* * Author: islands * Created Time: 2015/7/30 9:25:47 * File Name: test.cpp */ #in

NTT(快速数论变换)用到的各种素数及原根

NTT(快速数论变换)用到的各种素数及原根 g是mod(r * 2 ^ k + 1)的原根 r * 2 ^ k + 1 r k g 3 1 1 2 5 1 2 2 17 1 4 3 97 3 5 5 193 3 6 5 257 1 8 3 7681 15 9 17 12289 3 12 11 40961 5 13 3 65537 1 16 3 786433 3 18 10 5767169 11 19 3 7340033 7 20 3 23068673 11 21 3 104857601 25 2

数论 (大数,小费马定理,欧拉定理,威尔逊定理,快速数论变换(NNT)模版)

1 Java大数 2 import java.util.*; 3 import java.math.*; 4 public class Main{ 5 public static void main(String args[]){ 6 Scanner cin = new Scanner(System.in); 7 BigInteger a, b; 8 9 //以文件EOF结束 10 while (cin.hasNext()){ 11 a = cin.nextBigInteger(); 12 b

模板 NTT 快速数论变换

NTT裸模板,没什么好解释的 这种高深算法其实也没那么必要知道原理 1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define N (1<<17)+10 5 #define ll long long 6 using namespace std; 7 8 ll inv3,invl; 9 int r[N]; 10 ll A[N],B[N],C[N],mulwn[N],invw

BZOJ 3992 Sdoi2015 序列统计 快速数论变换

题目大意:给定n(n<=10^9),质数m(3<=m<=8000),1<=x=m,以及一个[0,m-1]区间内的集合S,求有多少长度为n的数列满足每个元素都属于集合S且所有元素的乘积mod m后=x 求原根,对S集合内每个元素取指标,然后搞出生成函数f(x) 那么答案就是(f(x))^n (mod x^(m-1),mod 1004535809) 上NTT用多项式快速幂搞一搞就好了 #include <cstdio> #include <cstring> #i

BZOJ 3992: [SDOI2015]序列统计 [快速数论变换 生成函数 离散对数]

3992: [SDOI2015]序列统计 Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 1017  Solved: 466[Submit][Status][Discuss] Description 小C有一个集合S,里面的元素都是小于M的非负整数.他用程序编写了一个数列生成器,可以生成一个长度为N的数列,数列中的每个数都属于集合S. 小C用这个生成器生成了许多这样的数列.但是小C有一个问题需要你的帮助:给定整数x,求所有可以生成出的,且满足数列中

数学(论)里的一些定理(莫比乌斯反演,傅立叶变换,数论变换...)

莫比乌斯反演 莫比乌斯反演在数论中占有重要的地位,许多情况下能大大简化运算.那么我们先来认识莫比乌斯反演公式. 定理:和是定义在非负整数集合上的两个函数,并且满足条件,那么我们得到结论 在上面的公式中有一个函数,它的定义如下: (1)若,那么 (2)若,均为互异素数,那么 (3)其它情况下 对于函数,它有如下的常见性质: (1)对任意正整数有 (2)对任意正整数有 线性筛选求莫比乌斯反演函数代码. void Init() { memset(vis,0,sizeof(vis)); mu[1] =