NTT的基础功用与拓展功能:
1.evaluate和interpolate
evaluate 的本质是选择n个点(假设f(x)的度为n),计算得到其值,因此根据定义可以直接进行代入计算。为了加快计算的过程选取 w n w_n wn的幂次(DFT问题即离散傅里叶变换),使用FFT算法来加快计算过程,将上述方法记作 N T T ( f ) NTT(f) NTT(f)
interpolate 的本质是根据n个点值计算得到对应的系数,据此可以列出方程直接求解或者利用矩阵进行求解(根据插值多项式的唯一性,解唯一)。为了加快计算的过程,当点值中的点都为 w n w_n wn的幂次时,可以使用 F F T − 1 FFT^{-1} FFT−1来进行计算,将上述方法记作 N T T − 1 ( f ) NTT^{-1}(f) NTT−1(f)
注:关于 w n w_n wn的选择
根据 w n w_n wn的定义,要求 w n 0 w_n^{0} wn0, w n 1 w_n^{1} wn1... w n n − 1 w_n^{n-1} wnn−1互不相同且 w n n = 1 w_n^{n}=1 wnn=1,当f(x)的数值需要mod N时,则需要找到 w n n w_n^{n} wnn同余1模上N。
FFT算法实现
cpp
#include<iostream>
#include<cmath>
using namespace std;
#define NMAX 10000007
int n, m;
int rev[NMAX];
struct complex {
//复数类
double x, y;//x+y*i的格式
complex(double xx = 0, double yy = 0) {
x = xx;
y = yy;
}
complex operator + (const complex b) {
return complex(x + b.x, y + b.y);
}
complex operator - (const complex b) {
return complex(x - b.x, y - b.y);
}
complex operator * (const complex b) {
return complex(x*b.x-y*b.y, x*b.y + y*b.x);
}
};
struct complex f[NMAX], g[NMAX];//需要在复数域上进行计算
struct complex Wn(int n,int type) {
//n代表的是等分的分数,type为1代表返回Wn,type为-1代表返回Wn^(-1)
double Pi = acos(-1.0);
return complex(cos(2 * Pi / n), type * sin(2 * Pi / n));
};
void test_for_complex() {
complex a = complex(1, 1);
complex b = complex(2, 2);
printf("%f+i*%f\n", a.x, a.y);
printf("%f+i*%f\n", b.x, b.y);
complex c = a + b;
printf("%f+i*%f\n", c.x, c.y);
c = a - b;
printf("%f+i*%f\n", c.x, c.y);
c = a * b;
printf("%f+i*%f\n", c.x, c.y);
}
void FFT(complex *a,int deg,int deg_len,int type) {
//1.进行比特反转
for (int i = 0; i < deg; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (deg_len - 1));
//for (int i = 0; i < deg; i++) cout << rev[i] << " ";
for (int i = 0; i < deg; i++) {
if(i<rev[i]) swap(a[i],a[rev[i]]);//注意这里只能交换一次
}
//2.进行迭代计算
for (int m = 2; m <= deg; m <<= 1) {//m代表的是合并后的个数
//2.1获取原根
struct complex wn = Wn(m, type);
for (int k = 0; k < deg; k += m) {//k代表的是待处理组(a[k...k+m-1])的第一个位置
//2.2对于每一组a[k...k+m/2-1]+a[k+m/2...k+m-1]=a[k...k+m-1]
complex w = complex(1, 0);
for (int j = 0; j < m / 2; j++) {//k+j指向a[k...k+m/2-1]
complex t = w * a[k + j + m / 2];
complex u = a[k + j];
a[k + j] = t + u;
a[k + j + m / 2] = u - t;
w = wn * w;
}
}
}
}
int main() {
cin >> n >> m;
for (int i = 0; i < n+1; i++) cin >> f[i].x;
for (int j = 0; j < m+1; j++) cin >> g[j].x;
//确定等分的分数(由于需要进行加速,所以分数应为2^n的形式)
int num = 1, len = 0;
while (num < (n + m+1)) {
num <<= 1;
len++;
}
FFT(f, num, len, 1);
FFT(g, num, len, 1);
for (int i = 0; i < num; i++) {
f[i] = f[i] * g[i];
}
FFT(f, num,len, -1);
for (int i = 0; i < n + m + 1; i++) cout << int(f[i].x/num + 0.5) << " ";
}
cpp
#include<iostream>
#include<cmath>
#include<string>
#include<string.h>
using namespace std;
#define NMAX 102
int n, m;
int rev[NMAX];
struct complex {
//复数类
double x, y;//x+y*i的格式
complex(double xx = 0, double yy = 0) {
x = xx;
y = yy;
}
complex operator + (const complex b) {
return complex(x + b.x, y + b.y);
}
complex operator - (const complex b) {
return complex(x - b.x, y - b.y);
}
complex operator * (const complex b) {
return complex(x*b.x-y*b.y, x*b.y + y*b.x);
}
};
//struct complex f[NMAX], g[NMAX];//需要在复数域上进行计算
struct complex Wn(int n,int type) {
//n代表的是等分的分数,type为1代表返回Wn,type为-1代表返回Wn^(-1)
double Pi = acos(-1.0);
return complex(cos(2 * Pi / n), type * sin(2 * Pi / n));
};
void test_for_complex() {
complex a = complex(1, 1);
complex b = complex(2, 2);
printf("%f+i*%f\n", a.x, a.y);
printf("%f+i*%f\n", b.x, b.y);
complex c = a + b;
printf("%f+i*%f\n", c.x, c.y);
c = a - b;
printf("%f+i*%f\n", c.x, c.y);
c = a * b;
printf("%f+i*%f\n", c.x, c.y);
}
void FFT(complex *a,int deg,int deg_len,int type) {
//1.进行比特反转
for (int i = 0; i < deg; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (deg_len - 1));
//for (int i = 0; i < deg; i++) cout << rev[i] << " ";
for (int i = 0; i < deg; i++) {
if(i<rev[i]) swap(a[i],a[rev[i]]);//注意这里只能交换一次
}
//2.进行迭代计算
for (int m = 2; m <= deg; m <<= 1) {//m代表的是合并后的个数
//2.1获取原根
struct complex wn = Wn(m, type);
for (int k = 0; k < deg; k += m) {//k代表的是待处理组(a[k...k+m-1])的第一个位置
//2.2对于每一组a[k...k+m/2-1]+a[k+m/2...k+m-1]=a[k...k+m-1]
complex w = complex(1, 0);
for (int j = 0; j < m / 2; j++) {//k+j指向a[k...k+m/2-1]
complex t = w * a[k + j + m / 2];
complex u = a[k + j];
a[k + j] = t + u;
a[k + j + m / 2] = u - t;
w = wn * w;
}
}
}
}
void FFT_new(complex* a, int deg, int deg_len, int type) {
//1.进行比特反转
for (int i = 0; i < deg; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (deg_len - 1));
//for (int i = 0; i < deg; i++) cout << rev[i] << " ";
for (int i = 0; i < deg; i++) {
if (i < rev[i]) swap(a[i], a[rev[i]]);//注意这里只能交换一次
}
//迭代计算
for (int m = 2; m <= deg; m <<=1) {
complex wn = Wn(m, type);//若为逆FTT,则为wn^(-1)
for (int j = 0; j < deg; j += m) {
//处理a[j...j+m-1]
complex w = complex(1, 0);
for (int k = 0; k < m / 2; k++) {
//框定左侧为a[j...j+m/2-1],由j+k游标指向
complex t = w * a[j + k + m / 2];//旋转因子
a[j + k + m / 2] = a[j + k] - t;
a[j + k] = a[j + k] + t;
w = wn * w;
}
}
}
//逆FTT需要乘上1/n
if (type == -1) {
for (int i = 0; i < deg; i++) a[i].x = int(a[i].x / deg + 0.5);
}
}
int read(string input, complex * f,int start, int end) {
//从string[start...end]中剥离出多项式系数
int deg = 0;
while(input[start] != '(') start++;//过滤掉不必要的空格使得从左括号开始
double coe = 0, exp = 0;
for (int i = start+1; i <= end; i++) {
if (input[i] == ' ') continue;//遇到空格则直接跳过
else if (input[i] == '+' || input[i] == ')') {
//cout << coe << " " << exp << endl;
f[int(exp)].x = coe;
if (exp > deg) deg = int(exp);
coe = 0, exp = 0;
}
else if (input[i] == 'a') {
//注意可能存在系数为1的情况
if (coe == 0) coe = 1;
i+=2;//跳过^
while (input[i] != '+' && input[i] != ')') {
exp = exp * 10 + input[i++] - '0';
}
i--;
}
else coe = coe * 10 + input[i] - '0';
}
return deg + 1;
}
void output(complex* f,int deg) {
for (int i = deg; i >= 0; i--) {
if (f[i].x > 0) {
if (i == 0) cout << int(f[i].x);
else if (int(f[i].x) != 1)cout << int(f[i].x) << "a^" << i;
else cout << "a^" << i;
if (i == 0)cout << endl;
else cout << "+";
}
}
}
int main() {
string input;
while (getline(cin, input)) {
struct complex f[NMAX], g[NMAX];//需要在复数域上进行计算
int pos =input.find('*');//根据题意,多项式的乘法仅仅只含两项
int len = strlen(input.c_str());
if (!(pos < len && pos >= 0)) {
cout << input << endl;//不含*,则直接输出
continue;
}
int deg_f = read(input, f,0,pos-1);
int deg_g = read(input, g,pos+1,len-1);
int deg = 1, deg_len = 0;
while (deg < (deg_f + deg_g)) deg <<= 1, deg_len++;
FFT_new(f, deg, deg_len, 1);
FFT_new(g, deg, deg_len, 1);
for (int i = 0; i < deg; i++) f[i] = f[i] * g[i];
FFT_new(f, deg, deg_len, -1);
output(f,deg);
}
}
NTT算法实现
cpp
void NTT(int* a, int n, int x) {
//参数设置:a代表待处理的数组,n为度,x代表的是是否是逆NTT
int len = 0, cn = n;
while (cn) {
len++;
cn >>= 1;
}
len--;
for (RI i = 1; i < n; ++i) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
//cout <<" " << i << " "<< rev[i] << endl;
}
//首先进行比特反转拷贝
for (RI i = 0; i < n; ++i) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (RI i = 1; i < n; i <<= 1) {//对于每一层
RI gn = ksm(G, (mod - 1) / (i << 1)); //代表的是旋转因子
for (RI j = 0; j < n; j += (i << 1)) { //以j到j+(i<<1)为一组,计算j开始的位置
RI t1, t2, g = 1;
for (RI k = 0; k < i; ++k, g = 1LL * g * gn % mod) {
t1 = a[j + k], t2 = 1LL * g * a[j + k + i] % mod; //数组a是如何处理得到的
a[j + k] = (t1 + t2) % mod, a[j + k + i] = (t1 - t2 + mod) % mod;
}
}
}
if (x == 1) return;
int ny = ksm(n, mod - 2);//计算得到n^(-1)
reverse(a + 1, a + n); //翻转[1...n-1]位,原因在于,求逆代入的是w^0,w^(-1),w^(-2),...w^(-n+1)
// w^0,w^(n-1),w(n-2),...w^1
//而此次计算代入的是w^0,w^1,w^2,...w^(n-1),因此进行反转即可
for (RI i = 0; i < n; ++i) a[i] = 1LL * a[i] * ny % mod;
}
2.计算多项式的乘法
问题概述:
计算 C ( x ) = A ( x ) ∗ B ( x ) C(x)=A(x)*B(x) C(x)=A(x)∗B(x)
解决方法1
直接按照手算的方式,展开计算,示例代码如下所示。假设A和B的度为n,则时间复杂度为 O ( n 2 ) O(n^2) O(n2)
python
a=[1,9]
b=[1,6]
c=[0]*(len(a)+len(b))
mod = 998244353
for i in range(len(a)):
for j in range(len(b)):
c[i+j] =(c[i+j] + a[i]*b[j]) % mod
for i in range(len(c)):
print(c[i])
#print(1)
解决办法2
- 首先估算 C ( x ) C(x) C(x)的度为 d e g c deg_c degc
- 计算得到 d e g deg deg,使得 d e g = 2 i deg=2^i deg=2i,且 d e g > d e g c deg>deg_c deg>degc
- 计算向量 a = N T T ( A , d e g ) a=NTT(A,deg) a=NTT(A,deg),向量 b = N T T ( B , d e g ) b=NTT(B,deg) b=NTT(B,deg)
- 计算向量 c = a ∗ b c=a*b c=a∗b
- 向量 C = N T T − 1 ( c , d e g ) C=NTT^{-1}(c,deg) C=NTT−1(c,deg)对应了 C ( x ) C(x) C(x)的各个系数
主体思想:
由于 C ( x ) C(x) C(x)的度为 d e g c deg_c degc,因此至少需要 d e g c deg_c degc个点值来推算 C ( x ) C(x) C(x)的系数,因此需要在 A ( x ) A(x) A(x)和 B ( x ) B(x) B(x)上至少取 d e g c deg_c degc个点值。由于NTT要求 d e g deg deg为 2 n 2^n 2n,因此需要进行第1步和第2步。
cpp
#include<iostream>
using namespace std;
int read() {
int q = 0; char ch = ' ';
while (ch < '0' || ch>'9') ch = getchar();
while (ch >= '0' && ch <= '9') q = q * 10 + ch - '0', ch = getchar();
return q;
}
#define RI register int
const int mod = 998244353, G = 3, N = 2100000;
int n;
int a[N], b[N], c[N], rev[N];
//根据小费马定理,a^(p-1) 同余 1 mod p (p为素数) a^(p-1)=a*a^(p-2),因此a^(-1) = a^(p-2),使用快速幂来计算a^(p-2)
int ksm(int x, int y) {
//快速幂,计算x^y
int re = 1;
for (; y; y >>= 1, x = 1LL * x * x % mod) {
if (y & 1) re = 1LL * re * x % mod;
}
return re;
}
void NTT(int* a, int n, int x) {
//Q:为什么这里仅仅只是考虑了模式的度,而不考虑模式的具体公式
//参数设置:a代表待检测的数组,n为度,x代表的是是否是逆NTT
int len = 0, cn = n;
while (cn) {
len++;
cn >>= 1;
}
len--;
for (RI i = 1; i < n; ++i) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
//cout <<" " << i << " "<< rev[i] << endl;
}
//首先进行比特反转拷贝
for (RI i = 0; i < n; ++i) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (RI i = 1; i < n; i <<= 1) {//对于每一层
RI gn = ksm(G, (mod - 1) / (i << 1)); //代表的是旋转因子
for (RI j = 0; j < n; j += (i << 1)) { //以j到j+(i<<1)为一组,计算j开始的位置
RI t1, t2, g = 1;
for (RI k = 0; k < i; ++k, g = 1LL * g * gn % mod) {
t1 = a[j + k], t2 = 1LL * g * a[j + k + i] % mod; //数组a是如何处理得到的
a[j + k] = (t1 + t2) % mod, a[j + k + i] = (t1 - t2 + mod) % mod;
}
}
}
if (x == 1) return;
int ny = ksm(n, mod - 2);//计算得到n^(-1)
reverse(a + 1, a + n); //翻转[1...n-1]位,原因在于,求逆代入的是w^0,w^(-1),w^(-2),...w^(-n+1)
// w^0,w^(n-1),w(n-2),...w^1
//而此次计算代入的是w^0,w^1,w^2,...w^(n-1),因此进行反转即可
for (RI i = 0; i < n; ++i) a[i] = 1LL * a[i] * ny % mod;
}
void test_for_ntt() {
//使用NTT计算一般的多项式乘法的注意点:
//1.首先需要估计结果的度,即为所有式子度之和,
//2.NTT的度要求为2^n,因此需要找到最小的数,满足2^n的形式,同时需要大于估计的结果的度
//3.NTT计算一般多项式乘的过程为,首先计算对于不同的函数,orz个不同的变量对应的值
//然后按照计算公式计算得到对应的结果多项式在orz个不同的变量处的取值
//最后逆NTT变换,得到结果多项式的系数
int a[20] = { 1, 9};
NTT(a, 2, 1);
NTT(a, 2, -1);
int b[20] = { 1, 6};
NTT(b, 2, 1);
NTT(b, 2, -1);
int c[20];
int deg = 4; //NTT中只能使用2^n来进行使用
NTT(a, deg, 1);
NTT(b, deg, 1);
for (int i = 0; i < deg; i++) {
c[i] = 1LL * a[i] * b[i] % mod;
}
NTT(c, deg, -1);
for (int i = 0; i < deg; i++) {
cout << c[i] << endl;
}
}
int main()
{
test_for_ntt();
}
例题
注:公式推导过程链接
整体思路为:
为了计算mod x d e g x^{deg} xdeg,先计算 mod x ( d e g + 1 ) / 2 x^{(deg+1)/2} x(deg+1)/2,然后利用公式计算mod x d e g x^deg xdeg。具体来说,若deg为奇数,则计算 m o d x ( d e g + 1 ) / 2 mod x^{(deg+1)/2} modx(deg+1)/2 ,利用公式计算得到 mod x d e g + 1 x^{deg+1} xdeg+1的逆,又因为当a≡b mod x n x^n xn 且n>m时,a≡b mod x m x^m xm,则计算结果等于模 x d e g x^{deg} xdeg的逆;若deg为偶数,则计算mod x d e g / 2 x^{deg/2} xdeg/2,利用公式计算得到mod x d e g x^{deg} xdeg
cpp
#include<iostream>
using namespace std;
int read() {
int q = 0; char ch = ' ';
while (ch < '0' || ch>'9') ch = getchar();
while (ch >= '0' && ch <= '9') q = q * 10 + ch - '0', ch = getchar();
return q;
}
#define RI register int
const int mod = 998244353, G = 3, N = 2100000;
int n;
int a[N], b[N], c[N], rev[N];
//根据小费马定理,a^(p-1) 同余 1 mod p (p为素数) a^(p-1)=a*a^(p-2),因此a^(-1) = a^(p-2),使用快速幂来计算a^(p-2)
int ksm(int x, int y) {
//快速幂,计算x^y
int re = 1;
for (; y; y >>= 1, x = 1LL * x * x % mod) {
if (y & 1) re = 1LL * re * x % mod;
}
return re;
}
void NTT(int* a, int n, int x) {
//Q:为什么这里仅仅只是考虑了模式的度,而不考虑模式的具体公式
//参数设置:a代表待检测的数组,n为度,x代表的是是否是逆NTT
int len = 0, cn = n;
while (cn) {
len++;
cn >>= 1;
}
len--;
for (RI i = 1; i < n; ++i) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
//cout <<" " << i << " "<< rev[i] << endl;
}
//首先进行比特反转拷贝
for (RI i = 0; i < n; ++i) if (i < rev[i]) swap(a[i], a[rev[i]]);
for (RI i = 1; i < n; i <<= 1) {//对于每一层
RI gn = ksm(G, (mod - 1) / (i << 1)); //代表的是旋转因子
for (RI j = 0; j < n; j += (i << 1)) { //以j到j+(i<<1)为一组,计算j开始的位置
RI t1, t2, g = 1;
for (RI k = 0; k < i; ++k, g = 1LL * g * gn % mod) {
t1 = a[j + k], t2 = 1LL * g * a[j + k + i] % mod; //数组a是如何处理得到的
a[j + k] = (t1 + t2) % mod, a[j + k + i] = (t1 - t2 + mod) % mod;
}
}
}
if (x == 1) return;
int ny = ksm(n, mod - 2);//计算得到n^(-1)
reverse(a + 1, a + n); //翻转[1...n-1]位,原因在于,求逆代入的是w^0,w^(-1),w^(-2),...w^(-n+1)
// w^0,w^(n-1),w(n-2),...w^1
//而此次计算代入的是w^0,w^1,w^2,...w^(n-1),因此进行反转即可
for (RI i = 0; i < n; ++i) a[i] = 1LL * a[i] * ny % mod;
}
void work(int deg, int* a, int* b) {
//为了计算mod x^deg,先计算 mod x^((deg+1)/2),然后利用公式计算mod x^deg
//若deg为奇数,则计算mod x^((deg+1)/2) ,利用公式计算得到 mod x^(deg+1),又因为a==b mod x^n -> a==b mod x^m (n>m) ,则计算结果是可以适用于x^deg的
//若deg为偶数,则计算mod x^(deg/2),利用公式计算得到mod x^deg
//公式的计算过程如下(整个计算过程不涉及mod x^n,而是利用NTT计算得到一般的多项式乘法)
//使用NTT计算一般的多项式乘法的注意点:
//1.首先需要估计结果的度,即为所有式子度之和,此处的估计结果为2*deg,即deg<<1
//2.NTT的度要求为2^n,因此需要找到最小的数,满足2^n的形式,同时需要大于估计的结果的度,此处为orz
//3.NTT计算一般多项式乘的过程为,首先计算对于不同的函数,orz个不同的变量对应的值,即NTT(c, orz, 1), NTT(b, orz, 1);
//然后按照计算公式计算得到对应的结果多项式在orz个不同的变量处的取值
//最后逆NTT变换,得到结果多项式的系数,此处NTT(b, orz, -1);
if (deg == 1) { b[0] = ksm(a[0], mod - 2); return; }
work((deg + 1) >> 1, a, b);//这里为什么一定要加上1 a==b mod x^n -> a==b mod x^m (n>m)
//处理度,取orz为大于2*deg的最小2^n
RI len = 0, orz = 1;
while (orz < (deg << 1)) orz <<= 1, ++len;
cout << deg << " " << orz << endl;
for (RI i = 1; i < orz; ++i) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
//cout <<" " << i << " "<< rev[i] << endl;
}
//将数组a复制到数组c,即c同于a mod x^deg
for (RI i = 0; i < deg; ++i) c[i] = a[i];
for (RI i = deg; i < orz; ++i) c[i] = 0;
NTT(c, orz, 1), NTT(b, orz, 1);
for (RI i = 0; i < orz; ++i)
b[i] = 1LL * (2 - 1LL * c[i] * b[i] % mod + mod) % mod * b[i] % mod; //算数运算符% * /的优先级是一样的,从左到右计算即可
NTT(b, orz, -1);
//输出普通的多项式乘法
for (RI i = 0; i < orz; ++i) printf("%d,", b[i]);
cout << endl;
//进行模x^deg处理
for (RI i = deg; i < orz; ++i) b[i] = 0; //计算得到b mod x^deg
for (RI i = 0; i < orz; ++i) printf("%d,", b[i]);
cout << endl;
}
void test_for_ntt() {
//使用NTT计算一般的多项式乘法的注意点:
//1.首先需要估计结果的度,即为所有式子度之和,
//2.NTT的度要求为2^n,因此需要找到最小的数,满足2^n的形式,同时需要大于估计的结果的度
//3.NTT计算一般多项式乘的过程为,首先计算对于不同的函数,orz个不同的变量对应的值
//然后按照计算公式计算得到对应的结果多项式在orz个不同的变量处的取值
//最后逆NTT变换,得到结果多项式的系数
int a[20] = { 1, 9};
NTT(a, 2, 1);
NTT(a, 2, -1);
int b[20] = { 1, 6};
NTT(b, 2, 1);
NTT(b, 2, -1);
int c[20];
int deg = 4; //NTT中只能使用2^n来进行使用
NTT(a, deg, 1);
NTT(b, deg, 1);
for (int i = 0; i < deg; i++) {
c[i] = 1LL * a[i] * b[i] % mod;
}
NTT(c, deg, -1);
for (int i = 0; i < deg; i++) {
cout << c[i] << endl;
}
}
int main()
{
//test_for_ntt();
//cout << 3 % 5 * 2 << endl;
n = read();
for (RI i = 0; i < n; ++i) a[i] = read();
work(n, a, b);
for (RI i = 0; i < n; ++i) printf("%d ", b[i]);
return 0;
}
3.循环卷积
cpp
#include<iostream>
#include<cmath>
#define NMAX 2000
using namespace std;
int rev[NMAX];
int n, m;
struct complex {
double x, y;//x+y*i
complex(double xx = 0, double yy = 0) { x = xx, y = yy; }//构造函数
complex operator +(const complex b) {
return complex(x + b.x, y + b.y);
}
complex operator -(const complex b) {
return complex(x - b.x, y - b.y);
}
complex operator *(const complex b) {
return complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
}f[NMAX],g[NMAX];
struct complex Wn(int n,int type) {
double Pi = acos(-1.0);
return complex(cos(2 * Pi / n), type*sin(2 * Pi / n));
}
void FFT(struct complex* f, int deg, int deg_len,int type) {
//ftt的逆直接取Wn^(-1)
//首先进行比特反转
for (int i = 0; i < deg; i++) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (deg_len - 1));
}
for (int i = 0; i < deg; i++) {
if (i < rev[i]) swap(f[i], f[rev[i]]);
}
//迭代进行计算
for (int gap = 2; gap <= deg; gap <<= 1) {
complex w = Wn(gap,type);
for (int g_start = 0; g_start < deg; g_start += gap) {
complex x = complex(1, 0);
for (int start = g_start; start < g_start + gap / 2; start++) {
complex u = f[start], v = f[start + gap / 2] * x;
f[start] = u + v;
f[start + gap / 2] = u - v;
x = x * w;
}
}
}
if (type == -1) {
for (int i = 0; i < deg; i++) {
f[i].x = f[i].x/ deg;
}
}
}
class NTT {
int a[NMAX] = { 1,9 }, b[NMAX] = { 1,6 };
int rev[NMAX] = {0};
int mod = 998244353;//模数
int g = 3;//mod简化剩余系上的生成元
int ksm(int a, int b, int mod) {
int ret = 1;
while (b) {
if (b & 1) ret = ((long long )ret) * a % mod;
a = ((long long )a) * a % mod;
b >>= 1;
}
return ret;
}
void ntt(int* f, int deg, int deg_len, int type) {
//首先进行比特反转
for (int i = 0; i < deg; i++) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (deg_len - 1));
}
for (int i = 0; i < deg; i++) {
if (i < rev[i]) swap(f[i], f[rev[i]]);
}
//迭代进行计算
for (int gap = 2; gap <= deg; gap <<= 1) {
int w = ksm(g, (mod - 1) / gap, mod);
for (int g_start = 0; g_start < deg; g_start += gap) {
int x = 1;
for (int start = g_start; start < g_start + gap / 2; start++) {
int u = f[start], v = (1LL * f[start + gap / 2] * x) % mod;
f[start] = (u + v)% mod;
f[start + gap / 2] = (u - v + mod) % mod;//做减法要加上mod来避免负数出现
x = (1LL* x * w) % mod;
}
}
}
//求逆运算处理
if (type == -1) {
for (int i = 1; i < deg / 2; i++) swap(f[i], f[deg - i]);
int inv_deg = ksm(deg, mod - 2, mod);
for (int i = 0; i < deg; i++) f[i] = 1LL * f[i] * inv_deg % mod;
}
//结果输出展示
for (int i = 0; i < deg; i++) cout << f[i] << " ";
cout << endl;
}
public:
void test_for_ntt() {
ntt(a, 4, 2, 1);
ntt(b, 4, 2, 1);
for (int i = 0; i < 4; i++) a[i] = (1LL *a[i] * b[i]) % mod;
ntt(a, 4, 2, -1);
for (int i = 0; i < 4; i++) cout << a[i] << " ";
}
};
void test_for_ftt() {
//计算最高次数分别为n和m的多项式f(x)和g(x)的卷积
cin >> n >> m;
for (int i = 0; i < n + 1; i++) cin >> f[i].x;
for (int j = 0; j < m + 1; j++) cin >> g[j].x;
//确定等分的分数(由于需要进行加速,所以分数应为2^n的形式)
int num = 1, len = 0;
while (num < (n + m + 1)) {
num <<= 1;
len++;
}
FFT(f, num, len, 1);
FFT(g, num, len, 1);
for (int i = 0; i < num; i++) {
//cout << f[i].x << " " << g[i].x << endl;
f[i] = f[i] * g[i];
}
FFT(f, num, len, -1);
for (int i = 0; i < n + m + 1; i++) cout << int(f[i].x + 0.5) << " ";
}
int main() {
NTT t;
t.test_for_ntt();
}