题意
给定 n 次多项式 F(x) 和 m 次多项式 G(x)
求:F(x) ⊗ G(x) 。 n, m ≤ 106
链接
https://www.luogu.org/problemnew/show/P3803
FFT
长度为
2
n
2^n
2n 的情况下
按照下标奇偶分类
显然有
A
(
x
)
=
A
1
(
x
2
)
+
x
A
2
(
x
2
)
A(x)=A_1(x^2)+xA_2(x^2)
A(x)=A1(x2)+xA2(x2)
代入
ω
n
k
=
e
2
i
π
k
n
\omega_n^k=e^{\frac{2i\pi k}{n}}
ωnk=en2iπk
A
(
ω
n
k
)
=
A
1
(
ω
n
2
k
)
+
ω
n
k
A
2
(
ω
n
2
k
)
A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k})
A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)
A
(
ω
n
k
+
n
/
2
)
=
A
1
(
ω
n
2
k
)
−
ω
n
k
A
2
(
ω
n
2
k
)
=
A
(
−
ω
n
k
)
A(\omega_n^{k+n/2})=A_1(\omega_n^{2k})-\omega_n^kA_2(\omega_n^{2k})=A(-\omega_n^k)
A(ωnk+n/2)=A1(ωn2k)−ωnkA2(ωn2k)=A(−ωnk)
(实际上我们有
ω
n
k
+
n
/
2
=
−
ω
n
k
\omega_n^{k+n/2} = -\omega_n^k
ωnk+n/2=−ωnk
逆变换只需要把单位根用其共轭复数替换,并将求出来的结果除以 n 。
( DFT 是多点求值,可以表示成系数矩阵乘上范德蒙德矩阵得到点值)
( IDFT 是反过来,那就是点值乘上范德蒙德矩阵的逆矩阵得到系数矩阵)
(然后求一求求一求搞一搞搞一搞就能得到关系了 -> Click Here
逆变换的具体推导过程也可以 -> Click Here
实现的时候我们把原数组下标二进制翻转就能直接得到递归到最底层的下标
利用它从最底层一层层向上合并即可
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
#include<ctime>
using namespace std;
#define R register
const int MAXN = 2097152+16;
int N, M, Limit = 1, Rev[MAXN], Log;
struct Complex
{
double r, i;
inline Complex(const double& a = 0, const double& b = 0)
{
r = a;
i = b;
}
inline Complex operator + (const Complex& o) const
{
return Complex(r + o.r, i + o.i);
}
inline Complex operator - (const Complex& o) const
{
return Complex(r - o.r, i - o.i);
}
inline void operator += (const Complex& o)
{
r += o.r, i += o.i;
}
inline void operator -= (const Complex& o)
{
r -= o.r, i -= o.i;
}
inline Complex operator * (const Complex& o) const
{
return Complex(r * o.r - i * o.i, r * o.i + i * o.r);
}
inline void operator *= (const Complex& o)
{
*this = *this * o;
}
}A[MAXN], B[MAXN], Wn[MAXN][2];
void FFT(Complex* A, const bool& Type = 0)
{
static Complex t;
for (R int i = 0; i < Limit; ++i) if(Rev[i] > i) swap(A[i], A[Rev[i]]);
for (R int Mid = 1; Mid < Limit; Mid <<= 1)
{
for (R int Len = Mid << 1, Pos = 0; Pos < Limit; Pos += Len)
{
for (R int Delta = 0; Delta < Mid; ++Delta)
{
t = Wn[Limit/Len*Delta][Type] * A[Pos + Mid + Delta];
A[Pos + Mid + Delta] = A[Pos + Delta] - t;
A[Pos + Delta] += t;
}
}
}
}
int main()
{
scanf("%d%d", &N, &M);
for (R int i = 0; i <= N; ++i) scanf("%lf", &A[i].r);
for (R int i = 0; i <= M; ++i) scanf("%lf", &B[i].r);
while (Limit <= N + M) Limit <<= 1, ++Log;
for (R int i = 0; i < Limit; ++i) Rev[i] = (Rev[i>>1]>>1) | ((i&1)<<(Log - 1));
const double tpi = 2.0 * acos(-1.0) / Limit;
register double tmp;
for (R int i = 0; i < Limit; ++i) tmp = tpi * i, Wn[i][0] = Complex(cos(tmp), sin(tmp)), Wn[i][1] = Complex(cos(tmp), -sin(tmp));
FFT(A); FFT(B);
for (R int i = 0; i < Limit; ++i) A[i] *= B[i];
FFT(A, 1);
for (R int i = 0; i <= N + M; ++i) printf("%d ", (int)(A[i].r/Limit+0.5));
return 0;
}
NTT
单位复根实现 FFT 的重要性质:
1.
ω
n
k
(
0
≤
k
<
n
)
\omega_n^k(0\le k<n)
ωnk(0≤k<n) 互不相同 → 点值合法
敏感的话容易想到
g
r
(
m
o
d
n
)
(
0
≤
r
<
n
)
g^r\pmod{n}(0\le r<n)
gr(modn)(0≤r<n) 也是如此
假设
p
=
q
n
+
1
,
ω
n
=
g
q
p=qn+1,\omega_n=g^q
p=qn+1,ωn=gq 且
n
n
n 是
2
2
2 的幂。 ←所以 ntt 也跟 ftt 一样,需要长度为
2
n
2^n
2n
NTT 在
(
m
o
d
p
)
\pmod{p}
(modp) 意义下进行。
2.
ω
2
n
2
k
=
ω
n
k
\omega_{2n}^{2k}=\omega_n^k
ω2n2k=ωnk → 分治①
ω
n
=
g
q
\omega_n=g^q
ωn=gq 那么
ω
2
n
=
g
q
/
2
\omega_{2n}=g^{q/2}
ω2n=gq/2 因为
p
=
q
n
+
1
=
q
2
⋅
2
n
+
1
p=qn+1=\frac{q}{2}\cdot{2n}+1
p=qn+1=2q⋅2n+1
那么
ω
2
n
2
k
=
g
k
q
=
ω
n
k
\omega_{2n}^{2k}=g^{kq}=\omega_n^k
ω2n2k=gkq=ωnk
3.
ω
n
k
+
n
/
2
=
−
ω
n
k
\omega_{n}^{k+n/2}=-\omega_n^k
ωnk+n/2=−ωnk
我们有
ω
n
k
+
n
/
2
=
g
(
k
+
n
/
2
)
q
=
g
k
q
⋅
g
n
q
/
2
\omega_n^{k+n/2}=g^{(k+n/2)q}=g^{kq}\cdot g^{nq/2}
ωnk+n/2=g(k+n/2)q=gkq⋅gnq/2
g
n
q
/
2
≡
−
1
(
m
o
d
p
)
g^{nq/2}\equiv-1\pmod{p}
gnq/2≡−1(modp) 是否成立呢?
显然
g
n
q
=
g
p
−
1
=
g
ϕ
(
p
)
≡
1
(
m
o
d
p
)
g^{nq}=g^{p-1}=g^{\phi(p)}\equiv1\pmod{p}
gnq=gp−1=gϕ(p)≡1(modp)
g
n
q
/
2
=
±
1
g^{nq/2}=\pm1
gnq/2=±1 且
g
0
=
1
≠
g
n
q
/
2
g^0=1\ne g^{nq/2}
g0=1̸=gnq/2 所以
g
n
q
/
2
=
−
1
g^{nq/2}=-1
gnq/2=−1
那么
ω
n
k
+
n
/
2
≡
−
ω
n
k
(
m
o
d
p
)
\omega_n^{k+n/2\equiv-\omega_n^k}\pmod{p}
ωnk+n/2≡−ωnk(modp)
4.
k
≠
0
,
∑
r
=
0
n
−
1
ω
n
k
r
=
0
k\ne0,\sum\limits_{r=0}^{n-1}\omega_n^{kr}=0
k̸=0,r=0∑n−1ωnkr=0
卜难证啊 一模一样
具体的 -> Click Here
然后换一换单位根就可以快乐 ntt
只要数据不会爆模数就可以啦?
998244353
=
119
∗
2
23
+
1
,
g
=
3
998244353=119*2^{23}+1,g=3
998244353=119∗223+1,g=3
1004535809
,
g
=
3
1004535809,g=3
1004535809,g=3
忘记了或者遇到没见过的 ntt 模数可以手打一个找原根?
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
#include<ctime>
using namespace std;
const int MAXN = 2929229;
const long long p = 998244353;
const long long g = 3;
int n, m, Lim = 1, Log;
long long A[MAXN], B[MAXN], Wn[MAXN][2];
int Rev[MAXN];
long long qpowm(long long a, long long b)
{
long long ret = 1;
a %= p;
while (b)
{
if (b & 1)
{
ret *= a;
ret %= p;
}
a *= a;
a %= p;
b >>= 1;
}
return ret;
}
inline long long Adjust(long long x)
{
x = (x % p) + p;
return (x>=p)?(x-p):x;
}
void NTT(long long *a, const bool& Type = 0)
{
for (register int i = 0; i < Lim; ++i) if (Rev[i] > i) swap(a[i], a[Rev[i]]);
register long long t;
for (register int Mid = 1; Mid < Lim; Mid <<= 1)
{
for (register int Len = Mid << 1, Pos = 0, qwq = Lim / Len; Pos < Lim; Pos += Len)
{
for (register int Sub = 0; Sub < Mid; ++Sub)
{
t = Wn[qwq*Sub][Type] * a[Pos + Mid + Sub];
a[Pos + Mid + Sub] = Adjust(a[Pos + Sub] - t);
a[Pos + Sub] = (a[Pos + Sub] + t) % p;
// cout << a[Pos+Mid+Sub] << " " << a[Pos+Mid] << endl;
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
for (register int i = 0; i <= n; ++i) scanf("%lld", &A[i]);
for (register int i = 0; i <= m; ++i) scanf("%lld", &B[i]);
n += m;
while (Lim <= n) Lim <<= 1, ++Log;
for (register int i = 0; i < Lim; ++i) Rev[i] = (Rev[i>>1]>>1)|((i&1)<<(Log-1));
const long long q = (p - 1) / Lim;
register long long t = qpowm(g, q);
const long long G = qpowm(qpowm(g, p-2), q);
for (register int i = 0; i < Lim; ++i)
{
Wn[i][0] = (!i) ? 1 : (Wn[i-1][0] * t % p);
Wn[i][1] = (!i) ? 1 : (Wn[i-1][1] * G % p);
// cout << Wn[i][0] << " " << Wn[i][1] << endl;
}
NTT(A); NTT(B);
for (register int i = 0; i < Lim; ++i) A[i] = A[i] * B[i] % p;
NTT(A, 1);
t = qpowm(Lim, p - 2);
for (register int i = 0; i <= n; ++i) printf("%lld ", A[i] * t % p);
return 0;
}
(无关)
才知道f社出新作了
火星了 两 个 月(震声)
我自尽罢
我去撒库拉抹油了.jpg