原题链接
题目大意
给定一个
n
n
n 次多项式
F
(
x
)
F(x)
F(x),和一个
m
m
m 次多项式
G
(
x
)
G(x)
G(x)。
请求出
F
(
x
)
F(x)
F(x) 和
G
(
x
)
G(x)
G(x) 的卷积。
输入格式
第一行两个整数
n
,
m
n,m
n,m。
接下来一行
n
+
1
n+1
n+1 个数字,从低到高表示
F
(
x
)
F(x)
F(x) 的系数。
接下来一行
m
+
1
m+1
m+1 个数字,从低到高表示
G
(
x
)
G(x)
G(x) 的系数。
输出格式
一行 n + m + 1 n+m+1 n+m+1 个数字,从低到高表示 F ( x ) ⋅ G ( x ) F(x) \cdot G(x) F(x)⋅G(x) 的系数。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input
1 2
1 2
1 2 1
S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output
1 4 5 2
H
i
n
t
&
E
x
p
l
a
i
n
\mathbf{Hint\&Explain}
Hint&Explain
样例如图所示。
其中粉色虚线为
F
(
x
)
F(x)
F(x),绿色虚线为
G
(
x
)
G(x)
G(x),蓝色实线为
F
(
x
)
⋅
G
(
x
)
F(x)\cdot G(x)
F(x)⋅G(x)。
数据范围
保证输入中的系数大于等于
0
0
0 且小于等于
9
9
9。
对于
100
%
100\%
100% 的数据,
1
≤
n
,
m
≤
10
6
1 \le n, m \leq {10}^6
1≤n,m≤106。
解题思路
这篇博客会介绍一个和FFT差不多(?)的算法,NTT。
在介绍NTT之前,你需要知道一些前置的知识。
前置知识
1.FFT
\texttt{1.FFT}
1.FFT
不会FFT的同学可以看我写的关于FFT的详解。
Click
me!
\texttt{Click me!}
Click me!
2.
快
速
幂
\texttt{2.}快速幂
2.快速幂
不会快速幂的同学可以看我写的关于快速幂的详解。
Click
me!
\texttt{Click me!}
Click me!
接下来就是重头戏NTT了。
NTT
其实就是FFT的加强版。
首先,我们要知道一个东西,叫做 阶 。
设
a
,
p
a,p
a,p 互素,且
p
>
1
p>1
p>1。
对于满足不定方程
a
x
≡
1
(
m
o
d
p
)
a^x\equiv 1\pmod p
ax≡1(modp) 最小的正整数解
n
n
n,我们称之为
a
a
a 模
p
p
p 的阶,记作
δ
p
(
a
)
\delta_p(a)
δp(a)。
然后是 原根 。
设
p
p
p 是正整数,
a
a
a 是整数,若
δ
p
(
a
)
δ_p(a)
δp(a) 等于
ϕ
(
p
)
ϕ(p)
ϕ(p),则称
a
a
a 为模
p
p
p 的一个原根。
例如:
∵
δ
7
(
3
)
=
6
=
ϕ
(
7
)
∴
3
为
7
的
一
个
原
根
。
\because \delta_7(3)=6=\phi(7)\\\therefore 3\sf为\rm7\sf的一个原根。
∵δ7(3)=6=ϕ(7)∴3为7的一个原根。
其实NTT和FFT的不同就是把FFT里面的单位根替换成了原根,而原根也满足我们用到的单位根的性质。
直接把所有的
ω
n
\omega_n
ωn 替换成原根就得了。
但是原根如何计算呢?
只要记住一个结论:
ω
n
=
g
p
−
1
n
(
m
o
d
p
)
\large \omega_n=g^{\frac{p-1}n}\pmod p
ωn=gnp−1(modp)
这里给出
998244353
998244353
998244353 的原根是
3
3
3 。
而
3
3
3 对于
998244353
998244353
998244353 的逆元是
332748118
332748118
332748118。
上代码
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
#define int ll
typedef long long ll;
namespace Polynomial{
// The value of the variable below can be changed.
const int max_size_of_array=4000000;
// The value of the variable above can be changed.
const int maxSize=max_size_of_array+10;
const int g=3,invg=332748118;
const int p=998244353;
int resort[maxSize];
int ta[maxSize];
int tb[maxSize];
int c[maxSize];
int d[maxSize];
int e[maxSize];
int h[maxSize];
int o[maxSize];
int q[maxSize];
int lim,dig;
int power(int a,int b,int p)
{
int tar=1;
for(; b; b>>=1,a=(1ll*a*a)%p)
if(b&1)
tar=(1ll*tar*a)%p;
return tar%p;
}
void NTT(int *c,int lim,int state)
{
// cout<<"NTT"<<endl;
for(int i=0; i<lim; i++)
if(i<resort[i])
swap(c[i],c[resort[i]]);
for(int i=1; i<lim; i<<=1)
{
int W1n=power(g,1ll*(p-1)/(i<<1),p);
for(int Size=i<<1,j=0; j<lim; j+=Size)
{
int W=1;
for(int k=0; k<i; k++,W=(1ll*W*W1n)%p)
{
int x=(1ll*c[j+k]+p)%p,y=(1ll*W*c[j+i+k]+p)%p;
c[j+k]=(1ll*x+y+p)%p;
c[j+i+k]=(1ll*x-y+p)%p;
// if(j+k==0)
// cout<<"\t"<<c[j+k]<<"\tNTT"<<endl;
}
}
}
if(state==1)
return;
int temp=power(lim,p-2,p);
reverse(c+1,c+lim);
for(int i=0; i<lim; i++)
c[i]=1ll*c[i]*temp%p;
return;
}
void getMul(int *f,int *g,int lenf,int leng)
{
memset(o,0,sizeof(o));
memset(q,0,sizeof(q));
lim=1,dig=0;
while(lim<=lenf+leng)
lim<<=1,dig++;
for(int i=0; i<lim; i++)
resort[i]=(resort[i>>1]>>1)|((i&1)<<(dig-1));
NTT(f,lim,1);
NTT(g,lim,1);
for(int i=0; i<lim; i++)
f[i]=((f[i]*g[i])%p+p)%p;
NTT(f,lim,-1);
return;
}
}
using namespace Polynomial;
int a[4000010];
int b[4000010];
int n,m;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
/* Code */
cin>>n>>m;
for(int i=0; i<=n; i++)
cin>>a[i];
for(int i=0; i<=m; i++)
cin>>b[i];
getMul(a,b,n+1,m+1);
for(int i=0; i<=n+m; i++)
cout<<a[i]<<" ";
return 0;
}
完美切题 ∼ \sim ∼