下面的所有知识均属多项式范畴。
前言
本文略去多项式的定义,毕竟初中数学都讲过。
本文会出现一些可能没有见过的表达:
- 对于一个关于 x x x 的多项式 f ( x ) f(x) f(x),有时也称其最高次项次数为 f ( x ) f(x) f(x) 的度数。
- 多项式同样定义了四则运算,包括常数与多项式的四则运算和多项式之间的四则运算,同时也衍生出了同余符号 ≡ \equiv ≡、对数函数 ln ( f ( x ) ) \ln(f(x)) ln(f(x)),指数函数 exp ( f ( x ) ) \exp(f(x)) exp(f(x)) 等等。
- 下文一般采用 f ( x ) m o d x n f(x)~\bmod x^n f(x) modxn 来表示多项式 f ( x ) f(x) f(x) 的所有次数小于 n n n 的项之和。
- 下文有时会采用 [ x k ] f ( x ) [x^k]f(x) [xk]f(x) 表示多项式次数为 k k k 的项。
注意:本文的知识环环相扣,请勿乱序阅读。
拉格朗日插值
插值,通俗地讲,就是指通过多项式在某些点的值求出整个多项式的表达式。
而拉格朗日插值就是计算插值的一种方法,它支持通过 n + 1 n + 1 n+1 个点插出一个度数为 n n n 的多项式。
按惯例,献上模板题。
首先对于多项式 f ( x ) f(x) f(x),必然满足 f ( x ) ≡ f ( a ) ( m o d ( x − a ) ) f(x) \equiv f(a)~(\bmod (x - a)) f(x)≡f(a) (mod(x−a))。
证明:
转化为 f ( x ) − f ( a ) ≡ 0 ( m o d ( x − a ) ) f(x) - f(a) \equiv 0~(\bmod~(x - a)) f(x)−f(a)≡0 (mod (x−a))。
设左侧的多项式为 g ( x ) g(x) g(x),显然其常数项为 0 0 0。
又因为其第 i i i 项为 p i ( x i − a i ) p_i(x^i - a^i) pi(xi−ai)( p i p_i pi 为系数),并且 x i − a i x^i - a^i xi−ai 必定可以分解为 ( x − a ) ⋅ q ( x ) (x - a) \cdot q(x) (x−a)⋅q(x) 的形式( q ( x ) q(x) q(x) 为另一个多项式),所以得证。
所以我们可以得到一个关于
f
(
x
)
f(x)
f(x) 线性同余方程组:
{
f
(
x
)
≡
y
1
(
m
o
d
(
x
−
x
1
)
)
f
(
x
)
≡
y
2
(
m
o
d
(
x
−
x
2
)
)
f
(
x
)
≡
y
3
(
m
o
d
(
x
−
x
3
)
)
⋮
f
(
x
)
≡
y
n
(
m
o
d
(
x
−
x
n
)
)
\left\{ \begin{matrix} f(x) \equiv y_1~(\bmod (x - x_1)) \\ f(x) \equiv y_2~(\bmod (x - x_2)) \\ f(x) \equiv y_3~(\bmod (x - x_3)) \\ \vdots \\ f(x) \equiv y_n~(\bmod (x - x_n)) \end{matrix} \right.
⎩⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎧f(x)≡y1 (mod(x−x1))f(x)≡y2 (mod(x−x2))f(x)≡y3 (mod(x−x3))⋮f(x)≡yn (mod(x−xn))
这里可以使用中国剩余定理解方程组(这里就没有必要关注它们是否互质了)。
我们可以设 M = ∏ i = 1 n ( x − x i ) M = \prod_{i = 1}^n (x - x_i) M=∏i=1n(x−xi), M i = M ( x − x i ) = ∏ j ≠ i ( x − x j ) M_i = \frac{M}{(x - x_i)} = \prod_{j \ne i} (x - x_j) Mi=(x−xi)M=∏j=i(x−xj)。
则 M i M_i Mi 在模 x − x i x - x_i x−xi 意义下的逆元为 ∏ j ≠ i 1 x i − x j \prod_{j \ne i}\frac{1}{x_i - x_j} ∏j=ixi−xj1。
(因为 M i M i − 1 = ∏ j ≠ i x − x j x i − x j ≡ 1 ( m o d ( x − x i ) ) M_i M_i^{-1} = \prod_{j \ne i}\frac{x - x_j}{x_i - x_j} \equiv 1~(\bmod (x - x_i)) MiMi−1=∏j=ixi−xjx−xj≡1 (mod(x−xi)),则对于每一个 j ≠ i j \ne i j=i 都存在 $ x - x_j \equiv x_i - x_j~(\bmod (x - x_i))$,移项可得 x − x i ≡ 0 ( m o d ( x − x i ) ) x - x_i \equiv 0~(\bmod (x - x_i)) x−xi≡0 (mod(x−xi)),这显然是成立的。)
所以有 f ( x ) = ∑ i = 1 n y i ∏ j ≠ i x − x j x i − x j ( m o d M ) f(x) = \sum_{i = 1}^n y_i\prod_{j \ne i} \frac{x - x_j}{x_i - x_j}~(\bmod~M) f(x)=∑i=1nyi∏j=ixi−xjx−xj (mod M),它在模意义下是唯一的。
(实际上有另外的证明方式证明了其在通常意义下也是唯一的,这里略去。)
本题中,我们只需要代入 k k k 求解即可,时间复杂度为 O ( n 2 ) \mathcal{O}(n^2) O(n2)。不过更多时候,我们需要求出其所有系数,也可以做到 O ( n 2 ) \mathcal{O}(n^2) O(n2),甚至还存在更低的复杂度,这里就不展开介绍了。
代码就不贴了。
多项式乘法
多项式乘法的定义,这里就不必多言了,初中数学都讲过。
代数形式:
设有多项式 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n f(x) = a_0 + a_1 x+ a_2x^2 + \cdots+ a_nx_n f(x)=a0+a1x+a2x2+⋯+anxn, g ( x ) = b 0 + b 1 x + b 2 x 2 + ⋯ + b m x m g(x) = b_0 + b_1 x+ b_2x^2 + \cdots+ b_mx_m g(x)=b0+b1x+b2x2+⋯+bmxm。
则它们的乘法积为 t ( x ) = c 0 + c 1 x + c 2 x 2 + ⋯ + c n + m x n + m t(x) = c_0 + c_1 x + c_2 x^2 + \cdots +c_{n + m}x^{n + m} t(x)=c0+c1x+c2x2+⋯+cn+mxn+m。
其中,对于 t ( x ) t(x) t(x) 的系数 c k c_k ck 有公式: c k = ∑ i = 0 k a i b k − i c_k = \sum_{i = 0}^k a_i b_{k - i} ck=∑i=0kaibk−i。
可以发现这个公式的形式十分类似卷积。但请注意:它们不是同一个东西,不要弄混淆了。
事实上,多项式乘法就是用于求卷积的。如果您对卷积感兴趣,建议百度。
现在,给出两个多项式 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 的系数,求出它们的乘法积 t ( x ) t(x) t(x)。 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 的次数小于 1 0 5 10^5 105。
根据上面的公式,我们可以得到一个
O
(
n
2
)
\mathcal{O}(n^2)
O(n2) 的优秀算法。
我们考虑换一种思想计算这个乘法积。
众所周知,有这样一条著名的定理:任意 k k k 个点可以唯一确定一个至多为 k − 1 k - 1 k−1 次的多项式。
所以我们得到了一个新的思路:先求出足够多的 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 的点值,将它们用 O ( n ) \mathcal{O}(n) O(n) 的复杂度乘起来(横坐标不变,纵坐标相乘),再将这些点转化为一个多项式(上面提到了这样的过程叫做插值)。
这种方法叫做离散傅里叶变换(即著名的 DFT \operatorname{DFT} DFT)。
问题是,如果钦定一些 x x x 去求对应的 f ( x ) f(x) f(x) 的值,时间复杂度还是 O ( n 2 ) \mathcal{O}(n^2) O(n2) 的。
所以,下面介绍快速傅里叶变换,同时会介绍快速数论变换(即著名的 FFT \operatorname{FFT} FFT 和 NTT \operatorname{NTT} NTT)。
还记得二次函数 y = x 2 y = x^2 y=x2 的性质吗?
它关于 y y y 轴对称,所以我们如果代入 x = k x = k x=k,同样可以得到 x = − k x = -k x=−k 时 y y y 的值。
这启发我们,可以选取一些特殊的 x x x,使得我们可以在求出一些点值的同时,快速地求出另外的一些点值。
这个时候,单位根就派上用场了。
先讲一些使得单位根能够应用于此的性质:
- 记 ω n \omega_n ωn 为 n n n 次单位根,即满足 ω n k = e 2 k π i n \omega_n^k = e^{\frac{2k\pi i}{n}} ωnk=en2kπi,而 e 2 k π i n = cos 2 k π n + i sin 2 k π n e^{\frac{2k\pi i}{n}}=\cos \frac{2k\pi}{n} + i \sin \frac{2k\pi}{n} en2kπi=cosn2kπ+isinn2kπ, i i i 为虚数单位。
(注意 n n n 次单位根的 n n n 一般取 2 2 2 的正整数次幂,而下文皆如此。)
这个计算公式不仅可以让我们计算出单位根的具体数值,并且可以得出以下性质:
- ω n n = cos 2 π + i sin 2 π = 1 = ω n 0 \omega_n^n = \cos 2\pi + i \sin 2\pi = 1 = \omega_n^0 ωnn=cos2π+isin2π=1=ωn0。
这条性质说明 n n n 次单位根存在长度为 n n n 的指数循环节;
- ω n n 2 = cos π + i sin π = − 1 \omega_n^{\frac{n}{2}} = \cos \pi + i \sin \pi= -1 ωn2n=cosπ+isinπ=−1。
这条性质间接地证明了 n n n 次单位根的最小指数循环节长度为 n n n,同时为下文折半引理的证明做了铺垫。
- 单位根消去引理: ω 2 n 2 k = ω n k \omega_{2n}^{2k} = \omega_n^k ω2n2k=ωnk。
根据公式有 ω 2 n 2 k = e 4 k π i 2 n = e 2 k π i n = ω n k \omega_{2n}^{2k}= e^{\frac{4k\pi i}{2n}} = e^{\frac{2k\pi i}{n}} = \omega_{n}^k ω2n2k=e2n4kπi=en2kπi=ωnk,得证。
这条性质可以做为沟通不同次单位根的桥梁,同时也证明了 n n n 次单位根的值域是 2 n 2n 2n 次单位根值域的真子集。
- 单位根折半引理: ω n k + n 2 = − ω n k \omega_n^{k+\frac{n}{2}} = -\omega_{n}^k ωnk+2n=−ωnk。
根据上面的性质有 ω n k + n 2 = ω n k ⋅ ω n n 2 = − ω n k \omega_n^{k + \frac{n}{2}} = \omega_n^k \cdot \omega_n^{\frac{n}{2}} = -\omega_n^k ωnk+2n=ωnk⋅ωn2n=−ωnk,得证。
这条性质可以做为沟通同次不同指数的单位根的桥梁,为 DFT \operatorname{DFT} DFT 的分治优化创造了可能。
而我们该如何运用单位根的这些性质呢?
我们将
f
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
a
3
x
3
f(x) = a_0 + a_1 x + a_2 x^2 + a_3x^3
f(x)=a0+a1x+a2x2+a3x3 做一个小小的改动:
f
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
a
3
x
3
=
a
0
+
a
2
x
2
+
a
1
x
+
a
3
x
3
=
(
a
0
+
a
2
x
2
)
+
x
(
a
1
+
a
3
x
2
)
=
f
1
(
x
2
)
+
x
f
2
(
x
2
)
\begin{aligned} f(x) &= a_0 + a_1 x + a_2 x^2 + a_3x^3 \\ &=a_0 + a_2 x^2 + a_1 x + a_3x^3 \\ &= (a_0 + a_2 x^2) + x(a_1 + a_3 x^2) \\ &= f_1(x^2) + xf_2(x^2) \end{aligned}
f(x)=a0+a1x+a2x2+a3x3=a0+a2x2+a1x+a3x3=(a0+a2x2)+x(a1+a3x2)=f1(x2)+xf2(x2)
可以发现,这样的变换,会使得原多项式被拆分为两个规模缩小了一半多项式。
所以,我们又有了一个新的算法模型:
我们考虑将 f ( x ) f(x) f(x) 拆分为两个规模缩小了一半的多项式 f 1 ( x ) , f 2 ( x ) f_1(x), f_2(x) f1(x),f2(x),然后对它们进行分治,接着,我们可以用 f 1 ( x ) , f 2 ( x ) f_1(x),f_2(x) f1(x),f2(x) 在 x = k 2 x = k^2 x=k2 处的点值计算出 f ( x ) f(x) f(x) 在 x = k x = k x=k 处的点值。如果分治时每一层的复杂度都能够做到 O ( n ) \mathcal{O}(n) O(n),那么总的时间复杂度就是 O ( n log 2 n ) \mathcal{O}(n \log_2 n) O(nlog2n)。
接下来,我们考虑利用单位根的性质,将每一层转化点值的操作复杂度变为 O ( n ) \mathcal{O}(n) O(n)。
如果我们将单位根
ω
n
k
\omega_{n}^k
ωnk 和
ω
n
k
+
n
2
\omega_n^{k + \frac{n}{2}}
ωnk+2n 分别代入多项式
f
(
x
)
f(x)
f(x):
f
(
ω
n
k
)
=
f
1
(
ω
n
2
k
)
+
ω
n
k
f
2
(
ω
n
2
k
)
=
f
1
(
ω
n
2
k
)
+
ω
n
k
f
2
(
ω
n
2
k
)
f
(
ω
n
k
+
n
2
)
=
f
1
(
ω
n
2
k
+
n
)
+
ω
n
k
+
n
2
f
2
(
ω
n
2
k
+
n
)
=
f
1
(
ω
n
2
k
)
−
ω
n
k
f
2
(
ω
n
2
k
)
f(\omega_{n}^{k}) = f_1(\omega_n^{2k}) + \omega_n^kf_2(\omega_n^{2k}) = f_1(\omega_{\frac{n}{2}}^{k}) + \omega_n^kf_2(\omega_{\frac{n}{2}}^{k}) \\ f(\omega_{n}^{k + \frac{n}{2}}) = f_1(\omega_n^{2k + n}) + \omega_n^{k + \frac{n}{2}}f_2(\omega_n^{2k + n}) = f_1(\omega_{\frac{n}{2}}^{k}) - \omega_n^kf_2(\omega_{\frac{n}{2}}^{k})
f(ωnk)=f1(ωn2k)+ωnkf2(ωn2k)=f1(ω2nk)+ωnkf2(ω2nk)f(ωnk+2n)=f1(ωn2k+n)+ωnk+2nf2(ωn2k+n)=f1(ω2nk)−ωnkf2(ω2nk)
可以发现,只要我们求出了
f
1
(
x
)
,
f
2
(
x
)
f_1(x),f_2(x)
f1(x),f2(x) 在
x
=
ω
n
2
k
x = \omega_{\frac{n}{2}}^k
x=ω2nk 处的点值,我们就可以直接得出
f
(
x
)
f(x)
f(x) 在
x
=
ω
n
k
,
x
=
ω
n
k
+
n
2
x = \omega_n^k,x = \omega_n^{k + \frac{n}{2}}
x=ωnk,x=ωnk+2n 两处的点值!
具体来说,我们只要在所有长度为 n n n 的多项式中求出在所有 n n n 次单位根处的点值即可。
这样,做到 O ( n ) \mathcal{O}(n) O(n) 的复杂度已经易如反掌了。
我们再来理一遍这个算法吧:(同时加入了一些很多细节)
- 我们找到一个 p p p,使得 p p p 是 2 2 2 的正整数次幂且 p ≥ n + m + 1 p \ge n + m + 1 p≥n+m+1。
这一步操作之前没有提到,但是很有必要,它为分治算法的进行提供了方便,这样我们就无需考虑多项式的度数;同时,这一步操作不会影响到点值的大小,也不会影响到最终插出的多项式的度数 (想一想,为什么)。
代码:( q q q 的作用下文会提及)
int p = 1, q = -1;
while(p <= n + m) ++q, p <<= 1;
- 接着,我们对两个多项式 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 分别跑 FFT \operatorname{FFT} FFT。在 FFT \operatorname{FFT} FFT 运行过程中,如果当前多项式长度为 1 1 1,直接返回系数;否则,将当前的多项式 f ( x ) f(x) f(x) 按照系数底数的奇偶性将项分类(这里请读者自己想一想为什么不可以直接对半分类),将其拆分为两个多项式,然后分治。
但是,如果这一步直接采用递归,时间似乎会有些吃不消。
我们不妨来探讨一下系数究竟是怎样分配的:(以
n
=
8
n = 8
n=8 为例)
{
a
0
,
a
1
,
a
2
,
a
3
,
a
4
,
a
5
,
a
6
,
a
7
}
↓
{
a
0
,
a
2
,
a
4
,
a
6
}
{
a
1
,
a
3
,
a
5
,
a
7
}
↓
{
a
0
,
a
4
}
{
a
2
,
a
6
}
{
a
1
,
a
5
}
{
a
3
,
a
7
}
↓
{
a
0
}
{
a
4
}
{
a
2
}
{
a
6
}
{
a
1
}
{
a
5
}
{
a
3
}
{
a
7
}
\{a_0, a_1, a_2, a_3, a_4, a_5, a_6, a_7\} \\ \downarrow \\ \{a_0,a_2,a_4,a_6\}~\{a_1,a_3,a_5,a_7\} \\ \downarrow \\ \{a_0, a_4\}~\{a_2, a_6\}~\{a_1, a_5\}~\{a_3, a_7\} \\ \downarrow \\ \{a_0\}~\{a_4\}~\{a_2\}~\{a_6\}~\{a_1\}~\{a_5\}~\{a_3\}~\{a_7\}
{a0,a1,a2,a3,a4,a5,a6,a7}↓{a0,a2,a4,a6} {a1,a3,a5,a7}↓{a0,a4} {a2,a6} {a1,a5} {a3,a7}↓{a0} {a4} {a2} {a6} {a1} {a5} {a3} {a7}
把每个系数的底数分离:
0 , 4 , 2 , 6 , 1 , 5 , 3 , 7 0, 4, 2, 6, 1, 5, 3, 7 0,4,2,6,1,5,3,7
转换成二进制:
000 , 100 , 010 , 110 , 001 , 101 , 011 , 111 000, 100, 010, 110, 001, 101, 011, 111 000,100,010,110,001,101,011,111
我们再把它们翻转过来:
000
,
001
,
010
,
011
,
100
,
101
,
110
,
111
000, 001, 010, 011, 100, 101, 110, 111
000,001,010,011,100,101,110,111
这不就是从小到大排序的吗?
所以我们就有了如下代码:(设 r i r_i ri 为分配完系数后第 i i i 个位置上的系数)
for(register int i = 0; i < p; i++) r[i] = (r[i >> 1] >> 1) | ((i & 1) << q);
解释一下:第 i i i 个位置的 r r r,不就等于第 ⌊ i 2 ⌋ \lfloor\frac{i}{2}\rfloor ⌊2i⌋ 个位置的 r r r 右移一位吗?
如果 i i i 是奇数,我们再在最高位补上一个 1 1 1 即可( q q q 的作用就在这里)。
以上过程称为 Rader \operatorname{Rader} Rader 排序。
- 用拆分出的两个多项式的点值,求出当前多项式的点值。
代码:( Complex \operatorname{Complex} Complex 为复数类,定义了构造函数,重定义了乘法)
inline void FFT(Complex *f, int n)
{
for(register int i = 0; i < n; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
for(register int len = 2; len <= n; len <<= 1)
{
int k = len >> 1;
Complex _w(cos(PI / k), t * sin(PI / k));
for(register int i = 0; i < n; i += len)
{
Complex w(1.0, 0.0);
for(register int j = 0; j < k; j++)
{
Complex u = f[i + j], v = w * f[i + j + k];
f[i + j] = u + v;
f[i + j + k] = u - v;
w = w * _w;
}
}
}
return;
}
- 接着,我们将 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 对应的点值相乘,时间复杂度为 O ( n ) \mathcal{O}(n) O(n)。
你以为结束了?并没有,我们还有第五步:
- 最后,我们将多项式插值出每一项的系数,算法结束。我们称这一步叫 IFFT \operatorname{IFFT} IFFT。
“算法结束” 四个字就在这里,近在咫尺,却又远在天边。
如果用拉格朗日插值计算,时间复杂度还是 O ( n 2 ) \mathcal{O}(n^2) O(n2),功亏一篑。
难道前面的分析过程都只能打水漂?
不不不,我们冷静分析一波。
看看下面这张图:

多项式乘法的过程实际上类似上图中的矩阵乘法。
那么我们想得到后面的系数矩阵,不就只要把中间的这个矩阵求逆就行了吗!
而这个逆矩阵非常特殊:

它等于原矩阵的每个值求倒数,再乘上一个 1 n \frac{1}{n} n1。
(然而笔者线性代数太菜了,无法解释其原因。)
既然无法解释,那就考虑推式子证明吧:
设 y k y_{k} yk 是 f ( x ) , g ( x ) f(x),g(x) f(x),g(x) 在傅里叶变换后,在 x = ω p k x = \omega_p^k x=ωpk 处点值相乘的结果。
我们设多项式
s
(
x
)
=
y
0
+
y
1
x
+
y
2
x
2
+
⋯
+
y
p
−
1
x
p
−
1
s(x) = y_0 + y_1 x + y_2 x^2 + \cdots + y_{p - 1} x ^{p - 1}
s(x)=y0+y1x+y2x2+⋯+yp−1xp−1 在
x
=
ω
n
0
,
ω
n
−
1
,
⋯
⋯
,
ω
n
−
(
p
−
1
)
x = \omega_n^{0}, \omega_n^{-1}, \cdots\cdots,\omega_n^{-(p - 1)}
x=ωn0,ωn−1,⋯⋯,ωn−(p−1) 处的点值分别为
u
0
∼
p
−
1
u_{0 \sim p - 1}
u0∼p−1,即满足:
u
k
=
∑
i
=
0
p
−
1
y
i
(
ω
p
−
k
)
i
u_k = \sum_{i = 0}^{p - 1} y_i(\omega_p^{-k})^i
uk=i=0∑p−1yi(ωp−k)i
现在我们要用它证明等式
c
k
=
u
k
p
c_k = \frac{u_k}{p}
ck=puk 成立,其中
c
k
c_k
ck 指最终
f
(
x
)
f(x)
f(x) 和
g
(
x
)
g(x)
g(x) 的乘法积的系数。
我们直接莽式子:
u
k
=
∑
i
=
0
p
−
1
y
i
(
ω
p
−
k
)
i
=
∑
i
=
0
p
−
1
(
∑
j
=
0
p
−
1
c
j
(
ω
p
i
)
j
)
(
ω
p
−
k
)
i
=
∑
i
=
0
p
−
1
∑
j
=
0
p
−
1
c
j
(
ω
p
j
−
k
)
i
=
∑
j
=
0
p
−
1
c
j
∑
i
=
0
p
−
1
(
ω
p
j
−
k
)
i
\begin{aligned} u_k &= \sum_{i = 0}^{p - 1} y_i(\omega_p^{-k})^i \\ &= \sum_{i = 0}^{p - 1} (\sum_{j = 0}^{p - 1}c_j(\omega_p^i)^j)(\omega_p^{-k})^i \\ &= \sum_{i = 0}^{p - 1} \sum_{j = 0}^{p - 1}c_j(\omega_p^{j - k})^i \\ &= \sum_{j = 0}^{p - 1}c_j \sum_{i = 0}^{p - 1} (\omega_p^{j - k})^i \end{aligned}
uk=i=0∑p−1yi(ωp−k)i=i=0∑p−1(j=0∑p−1cj(ωpi)j)(ωp−k)i=i=0∑p−1j=0∑p−1cj(ωpj−k)i=j=0∑p−1cji=0∑p−1(ωpj−k)i
然后对
∑
i
=
0
p
−
1
(
ω
p
j
−
k
)
i
\sum_{i = 0}^{p - 1} (\omega_p^{j - k})^i
∑i=0p−1(ωpj−k)i 这一部分分讨,设
v
=
j
−
k
v = j - k
v=j−k,则该式等于
∑
i
=
0
p
−
1
(
ω
p
v
)
i
\sum_{i = 0}^{p - 1} (\omega_p^v)^i
∑i=0p−1(ωpv)i。
在这个式子的意义下,这就是一个等比数列求和。
如果公比 ω p v = 1 \omega_p^v = 1 ωpv=1,即 v = 0 v = 0 v=0,则此时原式等于 p p p;
否则,套用等比数列公式,原式等于:
(
ω
v
)
p
−
1
ω
p
v
−
1
=
(
ω
p
)
v
−
1
ω
p
v
−
1
=
1
−
1
ω
p
v
−
1
=
0
\begin{aligned} &\frac{(\omega^v)^p - 1}{\omega_p^v - 1} \\ =&\frac{(\omega^p)^v - 1}{\omega_p^v - 1} \\ =&\frac{1 - 1}{\omega_p^v - 1} \\ =& 0 \end{aligned}
===ωpv−1(ωv)p−1ωpv−1(ωp)v−1ωpv−11−10
故当且仅当
v
=
j
−
k
=
0
v = j - k = 0
v=j−k=0,即
j
=
k
j = k
j=k 时,后面的这一部分才会产生贡献,则有
u
k
=
p
c
k
u_k = pc_k
uk=pck,即
c
k
=
1
p
u
k
c_k = \frac{1}{p}u_k
ck=p1uk。
得证。
然后可以发现, FFT \operatorname{FFT} FFT 和 IFFT \operatorname{IFFT} IFFT 的代码可以合为一份:
inline void FFT(Complex *f, int n, double t = 1.0)
{
for(register int i = 0; i < n; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
for(register int len = 2; len <= n; len <<= 1)
{
int k = len >> 1;
Complex _w(cos(PI / k), t * sin(PI / k));
for(register int i = 0; i < n; i += len)
{
Complex w(1.0, 0.0);
for(register int j = 0; j < k; j++)
{
Complex u = f[i + j], v = w * f[i + j + k];
f[i + j] = u + v;
f[i + j + k] = u - v;
w = w * _w;
}
}
}
if(t == -1) for(register int i = 0; i < n; i++) f[i].r /= n, f[i].i /= n;
return;
}
当运行 FFT \operatorname{FFT} FFT 时,我们令 t = 1 t = 1 t=1 即可;当运行 IFFT \operatorname{IFFT} IFFT 时,我们令 t = − 1 t = -1 t=−1 即可。
(注意:由于 cos \cos cos 和 sin \sin sin 的精度本身就十分地不稳定,加上复数乘法的运算次数较多,我们最后计算答案的时候需要处理这个误差。)
这样,算法真正大功告成啦!
优化:
可以发现,对于一个复数 a + b i a + bi a+bi,有 ( a + b i ) 2 = ( a 2 − b 2 ) + 2 a b i (a + bi)^2 = (a^2 - b^2) + 2abi (a+bi)2=(a2−b2)+2abi。
如果我们把 g ( x ) g(x) g(x) 放到 f ( x ) f(x) f(x) 的虚部上,对 f ( x ) f(x) f(x) 跑一次 FFT \operatorname{FFT} FFT,然后将 f ( x ) f(x) f(x) 的每个点值变为其平方。那么,虚部除以 2 2 2 不就是我们想要的值吗?
这样,我们就可以只跑一遍 FFT \operatorname{FFT} FFT,一遍 IFFT \operatorname{IFFT} IFFT 即可,常数优化至原来的 2 3 \frac{2}{3} 32。
然而, FFT \operatorname{FFT} FFT 的精度仍然是一个令人头疼的问题。我们想考虑一种方式,使得整个运算建立在整数域上。
整数,想到数论,有一个东西恰好符合其所有要求——原根。
关于原根的定义和性质,可以参考我的博客整除和同余相关理论。
可以发现,使得单位根可用于 FFT \operatorname{FFT} FFT 的性质,原根同样满足:
- n n n 次原根计算公式为 g n k = g k ( p − 1 ) n m o d p g_n^k = g^{\frac{k(p - 1)}{n}} \bmod~p gnk=gnk(p−1)mod p,其中 p p p 为模数, g g g 特指模 p p p 的最小原根。
这里的 p p p 一般取 998244353 998244353 998244353,它的最小原根等于 3 3 3,并且有 998244353 = 2 23 × 7 × 17 + 1 998244353 = 2^{23}\times 7\times 17 + 1 998244353=223×7×17+1,其中 2 23 = 8388608 2^{23} = 8388608 223=8388608,其大小完全可以满足我们的需求。
同样,我们也可以得出 g n n = g n 0 ≡ 1 ( m o d p ) g_n^n = g_n^0 \equiv 1~(\bmod ~p) gnn=gn0≡1 (mod p) 和 g n n 2 ≡ − 1 ( m o d p ) g_n^{\frac{n}{2}} \equiv -1~(\bmod ~p) gn2n≡−1 (mod p) 以及它们的推论。
- 原根同样存在消去引理和折半引理。
所以,在 FFT \operatorname{FFT} FFT 中对多项式的变换,在 NTT \operatorname{NTT} NTT 中同样适用。同时,也需要 Rader \operatorname{Rader} Rader 排序。
同理,对于 INTT \operatorname{INTT} INTT,原根同样可以在原来的基础上求倒数再乘 1 n \frac{1}{n} n1,只需要求逆元即可。
代码:
#define MOD 998244353
#define Min_G 3
#define Inv_Min_G 332748118
inline int Get_MOD(int k) { if(k >= MOD) k -= MOD; return k; }
inline int Pow(int a, int b)
{
int res = 1;
while(b)
{
if(b & 1) res = 1LL * res * a % MOD;
a = 1LL * a * a % MOD;
b >>= 1;
}
return res;
}
inline void NTT(int *f, int n, bool Inverse = false)
{
for(register int i = 0; i < n; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
for(register int len = 2; len <= n; len <<= 1)
{
int k = (len >> 1), g = Pow(Inverse ? Inv_Min_G : Min_G, (MOD - 1) / len);
for(register int i = 0; i < n; i += len)
{
int w = 1;
for(register int j = 0; j < k; j++)
{
int u = f[i + j], v = 1LL * w * f[i + j + k] % MOD;
f[i + j] = Get_MOD(u + v);
f[i + j + k] = Get_MOD(u - v + MOD);
w = 1LL * w * g % MOD;
}
}
}
if(Inverse)
{
int Inv_N = Pow(n, MOD - 2);
for(register int i = 0; i < n; i++) f[i] = 1LL * f[i] * Inv_N % MOD;
}
return;
}
后记:
实际上, FWT \operatorname{FWT} FWT 的思想和它们几乎是一模一样的,不过 FWT \operatorname{FWT} FWT 没有涉及到较多数学知识,会相对简单一些。
强推神佬的视频:快速傅里叶变换( FFT \operatorname{FFT} FFT)——有史以来最巧妙的算法?
强推神佬的博客: FFT \operatorname{FFT} FFT NTT \operatorname{NTT} NTT
多项式全家桶
到这里,正文才刚刚开始。。。
首先我们要知道一点求导和积分的前置知识。
对于要用到的结论,这里就直接摆出来了:
f
(
x
)
=
e
x
⇒
f
′
(
x
)
=
e
x
f
(
x
)
=
ln
x
⇒
f
′
(
x
)
=
1
x
f
(
x
)
=
a
x
b
⇒
f
′
(
x
)
=
a
b
x
b
−
1
(
f
(
x
)
±
g
(
x
)
)
′
=
f
′
(
x
)
±
g
′
(
x
)
(
f
(
g
(
x
)
)
′
=
f
′
(
g
(
x
)
)
∗
g
′
(
x
)
\begin{aligned} & f(x) = e^x \Rightarrow f'(x) = e^x \\ & f(x) = \operatorname{ln} x \Rightarrow f'(x) = \frac{1}{x} \\ & f(x) = ax^b \Rightarrow f'(x) = abx^{b - 1} \\ & (f(x) \pm g(x))' = f'(x) \pm g'(x) \\ & (f(g(x))' = f'(g(x)) * g'(x) \end{aligned}
f(x)=ex⇒f′(x)=exf(x)=lnx⇒f′(x)=x1f(x)=axb⇒f′(x)=abxb−1(f(x)±g(x))′=f′(x)±g′(x)(f(g(x))′=f′(g(x))∗g′(x)
其中
′
'
′ 表示求导。
所以我们可以推出多项式求导公式:
f
(
x
)
=
∑
i
=
0
n
a
i
x
i
⇒
f
′
(
x
)
=
∑
i
=
0
n
−
1
(
i
+
1
)
a
i
+
1
x
i
f(x) = \sum_{i = 0}^n a_i x^i \Rightarrow f'(x) = \sum_{i = 0}^{n - 1}(i + 1)a_{i + 1} x^i
f(x)=i=0∑naixi⇒f′(x)=i=0∑n−1(i+1)ai+1xi
当然,如果知道了多项式的导数,我们也可以用积分求出原多项式:
f
′
(
x
)
=
∑
i
=
0
n
a
i
x
i
⇒
f
(
x
)
=
∑
i
=
1
n
+
1
a
i
−
1
x
i
i
f'(x) = \sum_{i = 0}^na_ix^i \Rightarrow f(x) = \sum_{i = 1}^{n + 1}\frac{a_{i - 1}x^i}{i}
f′(x)=i=0∑naixi⇒f(x)=i=1∑n+1iai−1xi
注意:多项式积分是不能得到正确的常数项的。不过大部分题目在数据上做了保证,可以直接认为其为
0
0
0。
这两个过程的代码写起来就很简单了,时间复杂度都是 O ( n ) \mathcal{O}(n) O(n) 的:
求导:(把度数为 n − 1 n-1 n−1 的多项式 f ( x ) f(x) f(x) 求导,结果为 g ( x ) g(x) g(x))
inline void Get_Derivative(int *f, int *g, int n)
{
for(register int i = 1; i < n; i++)
g[i - 1] = 1LL * i * f[i] % MOD;
return g[n - 1] = 0, void();
}
积分:(把多项式 f ( x ) f(x) f(x) 求积分,结果为度数为 n − 1 n-1 n−1 的多项式 g ( x ) g(x) g(x))
inline void Get_Integral(int *f, int *g, int n)
{
for(register int i = n - 1; i >= 1; i--)
g[i] = 1LL * f[i - 1] * Pow(i, MOD - 2) % MOD;
return g[0] = 0, void();
}
在文章后面的部分,有很多地方会把一些式子通过求导变成方便计算的形式,然后再用积分求出答案。
以上结论已经能够满足大部分 OI \operatorname{OI} OI 题目的需求了。
如果对证明感兴趣,或者想知道更加严谨的结论,可以查找更多资料学习。(显然笔者也只会结论。。。)
多项式乘法逆元
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x),求出一个度数同为 n − 1 n - 1 n−1 多项式 g ( x ) g(x) g(x),满足 f ( x ) ∗ g ( x ) ≡ 1 ( m o d x n ) f(x)*g(x) \equiv 1~(\bmod x^n) f(x)∗g(x)≡1 (modxn)。
其中 ∗ * ∗ 表示多项式乘法。系数对 998244353 998244353 998244353 取模。
先给出算法:倍增。
假如我们已经求出了一个多项式 t t t,满足 f ( x ) ∗ t ( x ) ≡ 1 ( m o d x ⌈ n 2 ⌉ ) f(x) * t(x) \equiv 1~(\bmod~x^{\lceil\frac{n}{2}\rceil}) f(x)∗t(x)≡1 (mod x⌈2n⌉)。
又因为 f ( x ) ∗ g ( x ) ≡ 1 ( m o d x n ) ⇒ f ( x ) ∗ g ( x ) ≡ 1 ( m o d x ⌈ n 2 ⌉ ) f(x)*g(x)\equiv 1~(\bmod x^n) \Rightarrow f(x) * g(x) \equiv 1~(\bmod~x^{\lceil\frac{n}{2}\rceil}) f(x)∗g(x)≡1 (modxn)⇒f(x)∗g(x)≡1 (mod x⌈2n⌉)。
所以 f ( x ) ∗ ( g ( x ) − t ( x ) ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) f(x) * (g(x) - t(x)) \equiv 0~(\bmod~x^{\lceil\frac{n}{2}\rceil}) f(x)∗(g(x)−t(x))≡0 (mod x⌈2n⌉)。
f ( x ) f(x) f(x) 由于要求逆元,所以不可能为 0 0 0,那么有 g ( x ) − t ( x ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) g(x) - t(x) \equiv 0~(\bmod~x^{\lceil\frac{n}{2}\rceil}) g(x)−t(x)≡0 (mod x⌈2n⌉)。
平方后移项有: g 2 ( x ) ≡ 2 ∗ g ( x ) ∗ t ( x ) − t 2 ( x ) ( m o d x n ) g^2(x) \equiv 2 * g(x) * t(x) - t^2(x)~(\bmod~x^{n}) g2(x)≡2∗g(x)∗t(x)−t2(x) (mod xn)。
然后两边再乘上一个 f ( x ) f(x) f(x): f ( x ) ∗ g 2 ( x ) ≡ 2 ∗ f ( x ) ∗ g ( x ) ∗ t ( x ) − f ( x ) ∗ t 2 ( x ) ( m o d x n ) f(x) * g^2(x) \equiv 2 * f(x) * g(x) * t(x) - f(x) * t^2(x)~(\bmod~x^{n}) f(x)∗g2(x)≡2∗f(x)∗g(x)∗t(x)−f(x)∗t2(x) (mod xn)。
根据前面的结论,可以化简为: g ( x ) ≡ 2 ∗ t ( x ) − f ( x ) ∗ t 2 ( x ) ( m o d x n ) g(x) \equiv 2 * t(x) - f(x) * t^2(x)~(\bmod~x^{n}) g(x)≡2∗t(x)−f(x)∗t2(x) (mod xn)。
我们知道 f ( x ) f(x) f(x) 和 t ( x ) t(x) t(x),那么自然也可以求出 g ( x ) g(x) g(x) 了。
对于常数项,直接求出其逆元即可。
时间复杂度 O ( n log 2 n ) \mathcal{O}(n \log_2n) O(nlog2n)。
贴上代码:(注意数组之间的覆盖与清零)
inline void Init(int n)
{
p = 1, q = -1;
while(p < n) p <<= 1, ++q;
for(register int i = 0; i < p; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << q);
return;
}
inline void Get_Operation_Sequence(int n, int *seq)
{
seq[0] = 0;
while(n != 1)
{
seq[++seq[0]] = n;
n = (n + 1) >> 1;
}
seq[++seq[0]] = 1;
return;
}
inline void Get_Inverse_Element(int *f, int *g, int n)
{
Get_Operation_Sequence(n, seq[0]);
for(register int k = seq[0][0]; k >= 1; k--)
{
int m = seq[0][k];
if(m == 1)
{
g[0] = Pow(f[0], MOD - 2);
continue;
}
Init(m << 1);
memcpy(t, f, sizeof(int) * m);
fill(t + m, t + p, 0);
NTT(t, p), NTT(g, p);
for(register int i = 0; i < p; i++) g[i] = 1LL * g[i] * Get_MOD(2LL - 1LL * t[i] * g[i] % MOD + MOD) % MOD;
NTT(g, p, true), fill(g + m, g + p, 0);
continue;
}
}
多项式除法
(话说多项式除法的地位和多项式乘法比起来为何如此卑微。。。)
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x) 和一个度数为 m − 1 m - 1 m−1 多项式 g ( x ) g(x) g(x),请求出多项式 q ( x ) q(x) q(x) 和 r ( x ) r(x) r(x)。
满足:
- f ( x ) = g ( x ) ∗ q ( x ) + r ( x ) f(x) = g(x) * q(x) + r(x) f(x)=g(x)∗q(x)+r(x);
- q ( x ) q(x) q(x) 度数为 n − m n - m n−m, r ( x ) r(x) r(x) 度数小于 m − 1 m - 1 m−1;
所有系数对 998244353 998244353 998244353 取模。
没记错的话,初中数学老师讲过一种 “大除法”。
不过本题是在模意义下计算的,所以说这种方法不是很适用。
接下来,我们需要考虑用先前学过的知识
那么这操作就很关键了:系数翻转。
(为了方便,下文设 f ( x ) f(x) f(x) 度数为 n n n,设 g ( x ) g(x) g(x) 度数为 m m m,其余的度数变化同理。)
我们设对 f ( x ) f(x) f(x) 的第 0 ∼ n 0\sim n 0∼n 个系数翻转后为 f r ( n ) ( x ) f^{r(n)}(x) fr(n)(x)。
(注意下文不会刻意地区分 f r ( n ) ( x ) f^{r(n)}(x) fr(n)(x) 中 r ( n ) r(n) r(n) 和余数 r ( x ) r(x) r(x) 的区别。)
那么可以发现有:
f
r
(
n
)
(
x
)
=
∑
i
=
0
n
a
n
−
i
x
i
⇓
f
r
(
n
)
(
x
)
=
x
n
f
(
1
x
)
f^{r(n)}(x) = \sum_{i = 0}^{n} a_{n - i} x^i \\ \Downarrow \\ f^{r(n)}(x) = x^{n} f(\frac{1}{x})
fr(n)(x)=i=0∑nan−ixi⇓fr(n)(x)=xnf(x1)
又因为
f
(
1
x
)
=
g
(
1
x
)
∗
q
(
1
x
)
+
r
(
1
x
)
f(\frac{1}{x}) = g(\frac{1}{x}) * q(\frac{1}{x}) + r(\frac{1}{x})
f(x1)=g(x1)∗q(x1)+r(x1),所以:
f
r
(
n
)
(
x
)
=
x
n
∗
(
g
(
x
)
∗
q
(
x
)
+
r
(
x
)
)
=
x
n
∗
(
g
(
x
)
∗
q
(
x
)
)
+
x
n
r
(
x
)
=
x
m
∗
g
(
x
)
∗
x
n
−
m
∗
q
(
x
)
+
x
n
r
(
x
)
=
g
r
(
m
)
(
x
)
∗
q
r
(
n
−
m
)
(
x
)
+
r
r
(
n
)
(
x
)
\begin{aligned} f^{r(n)}(x) &= x^n * (g(x) * q(x) + r(x)) \\ &= x^n * (g(x) * q(x)) + x^n r(x) \\ &= x^m * g(x) * x^{n - m} * q(x) + x^nr(x) \\ &= g^{r(m)}(x) * q^{r(n - m)}(x) + r^{r(n)}(x) \end{aligned}
fr(n)(x)=xn∗(g(x)∗q(x)+r(x))=xn∗(g(x)∗q(x))+xnr(x)=xm∗g(x)∗xn−m∗q(x)+xnr(x)=gr(m)(x)∗qr(n−m)(x)+rr(n)(x)
然后,既然
r
(
x
)
r(x)
r(x) 的度数小于
m
m
m,那么翻转过后,
r
r
(
n
)
(
x
)
r^{r(n)}(x)
rr(n)(x) 的前
n
−
m
n - m
n−m 个系数就必然为
0
0
0。
所以,如果我们在 m o d x n − m + 1 \bmod~x^{n - m + 1} mod xn−m+1 意义下做上述运算,不仅可以去掉余数 r ( x ) r(x) r(x) 的影响,同时我们还可以发现,这样完美地保留了 q ( x ) q(x) q(x)。
那么有:
f
r
(
n
)
(
x
)
≡
g
r
(
m
)
(
x
)
∗
q
r
(
n
−
m
)
(
x
)
(
m
o
d
x
n
−
m
+
1
)
⇓
q
r
(
n
−
m
)
(
x
)
≡
g
r
(
m
)
(
x
)
f
r
(
n
)
(
x
)
(
m
o
d
x
n
−
m
+
1
)
⇓
q
(
x
)
=
(
g
r
(
m
)
(
x
)
f
r
(
n
)
(
x
)
)
r
(
n
−
m
)
(
m
o
d
x
n
−
m
+
1
)
f^{r(n)}(x) \equiv g^{r(m)}(x) * q^{r(n - m)}(x)~(\bmod~x^{n - m + 1}) \\ \Downarrow \\ q^{r(n - m)}(x) \equiv \frac{g^{r(m)}(x)}{f^{r(n)}(x)} ~(\bmod~x^{n - m + 1}) \\ \Downarrow \\ q(x) = (\frac{g^{r(m)}(x)}{f^{r(n)}(x)})^{r(n - m)} ~(\bmod~x^{n - m + 1})
fr(n)(x)≡gr(m)(x)∗qr(n−m)(x) (mod xn−m+1)⇓qr(n−m)(x)≡fr(n)(x)gr(m)(x) (mod xn−m+1)⇓q(x)=(fr(n)(x)gr(m)(x))r(n−m) (mod xn−m+1)
至于余数,我们直接把
g
(
x
)
g(x)
g(x) 和
q
(
x
)
q(x)
q(x) 乘起来,再用
f
(
x
)
f(x)
f(x) 减去即可。
代码:
inline void Multiply(int *f, int n, int *g, int m, int *v)
{
Init(n + m - 1);
memcpy(h, f, sizeof(int) * n), fill(h + n, h + p, 0);
memcpy(s, g, sizeof(int) * m), fill(s + m, s + p, 0);
NTT(h, p), NTT(s, p);
for(register int i = 0; i < p; i++) v[i] = 1LL * h[i] * s[i] % MOD;
return NTT(v, p, true), fill(v + n + m - 1, v + p, 0), void();
}
inline void Divide(int *f, int n, int *g, int m, int *v)
{
memcpy(s, g, sizeof(int) * m);
reverse(s, s + m);
Get_Inverse_Element(s, r, n - m + 1);
memcpy(t, f, sizeof(int) * n);
reverse(t, t + n);
Multiply(t, n, r, n - m + 1, v);
return reverse(v, v + n - m + 1);
}
inline void Get_Remainder(int *f, int n, int *g, int m, int *v, int *x)
{
Multiply(g, m, v, n - m + 1, x);
for(register int i = 0; i < m; i++) x[i] = Get_MOD(f[i] - x[i] + MOD);
return;
}
多项式开根
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x),求出一个度数同为 n − 1 n - 1 n−1 多项式 g ( x ) g(x) g(x),满足 g 2 ( x ) ≡ f ( x ) ( m o d x n ) g^2(x) \equiv f(x)~(\bmod x^n) g2(x)≡f(x) (modxn)。
保证有 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1。系数对 998244353 998244353 998244353 取模。
我们还是用倍增法来做。
临界条件: n = 1 n = 1 n=1 时, [ x 0 ] g ( x ) = 1 [x^0]g(x) = 1 [x0]g(x)=1。
设已经求出了一个多项式 t ( x ) t(x) t(x),满足 t 2 ( x ) ≡ f ( x ( m o d x ⌈ n 2 ⌉ ) t^2(x) \equiv f(x~(\bmod~x^{\lceil\frac{n}{2}\rceil}) t2(x)≡f(x (mod x⌈2n⌉)。
同时设 g ( x ) 2 ≡ f ( x ) ( m o d x n ) g(x)^2\equiv f(x)~(\bmod x^n) g(x)2≡f(x) (modxn)。
然后又是一波推式子:
t
2
(
x
)
≡
f
(
x
)
(
m
o
d
x
⌈
n
2
⌉
)
t
2
(
x
)
−
f
(
x
)
≡
0
(
m
o
d
x
⌈
n
2
⌉
)
(
t
2
(
x
)
−
f
(
x
)
)
2
≡
0
(
m
o
d
x
n
)
(
t
2
(
x
)
+
f
(
x
)
)
2
≡
4
∗
t
2
(
x
)
∗
f
(
x
)
(
m
o
d
x
n
)
(
t
2
(
x
)
+
f
(
x
)
2
∗
t
(
x
)
)
2
≡
f
(
x
)
(
m
o
d
x
n
)
t
2
(
x
)
+
f
(
x
)
2
∗
t
(
x
)
≡
g
(
x
)
(
m
o
d
x
n
)
t
(
x
)
2
+
f
(
x
)
2
∗
t
(
x
)
≡
g
(
x
)
(
m
o
d
x
n
)
\begin{aligned} & t^2(x) \equiv f(x)~(\bmod~x^{\lceil\frac{n}{2}\rceil}) \\ & t^2(x) - f(x)\equiv 0~(\bmod~x^{\lceil\frac{n}{2}\rceil}) \\ & (t^2(x) - f(x))^2 \equiv 0~(\bmod~x^n) \\ & (t^2(x) + f(x))^2 \equiv 4* t^2(x) * f(x)~(\bmod~x^n) \\ & (\frac{t^2(x) + f(x)}{2*t(x)})^2 \equiv f(x)~(\bmod~x^n) \\ & \frac{t^2(x) + f(x)}{2*t(x)} \equiv g(x)~(\bmod~x^n) \\ & \frac{t(x)}{2} + \frac{f(x)}{2 * t(x)} \equiv g(x)~(\bmod~x^n) \end{aligned}
t2(x)≡f(x) (mod x⌈2n⌉)t2(x)−f(x)≡0 (mod x⌈2n⌉)(t2(x)−f(x))2≡0 (mod xn)(t2(x)+f(x))2≡4∗t2(x)∗f(x) (mod xn)(2∗t(x)t2(x)+f(x))2≡f(x) (mod xn)2∗t(x)t2(x)+f(x)≡g(x) (mod xn)2t(x)+2∗t(x)f(x)≡g(x) (mod xn)
推到这里式子就好做了。时间复杂度
O
(
n
log
2
n
)
\mathcal{O}(n \log_2n)
O(nlog2n)。
当然还有一种常数较小的写法:在维护 t ( x ) t(x) t(x) 的同时维护 1 t ( x ) \frac{1}{t(x)} t(x)1,因为开根的递归结构和求逆是一样的。
贴上代码:(没有写这种常数较小的写法)
inline void Get_Sqrt(int *f, int *g, int n)
{
Get_Operation_Sequence(n, seq[1]);
for(register int k = seq[1][0]; k >= 1; k--)
{
int m = seq[1][k];
if(m == 1)
{
g[0] = Quadratic_Residue :: Solve(f[0]);
continue;
}
Init(m << 1);
memset(h, 0, sizeof(int) * p);
Get_Inverse_Element(g, h, m);
memcpy(s, f, sizeof(int) * m);
fill(s + m, s + p, 0);
NTT(g, p), NTT(h, p), NTT(s, p);
int Inv2 = Pow(2, MOD - 2);
for(register int i = 0; i < p; i++) g[i] = 1LL * Inv2 * Get_MOD(g[i] + 1LL * h[i] * s[i] % MOD) % MOD;
NTT(g, p, true), fill(g + m, g + p, 0);
continue;
}
return;
}
当然,还可以去做一下加强版,仅需要在求常数项时套上一个二次剩余即可。
写好二次剩余后,就只需要改 Get_Sqrt函数的第一行即可。
代码就不放出来了。
多项式对数函数
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x),求出一个度数同为 n − 1 n - 1 n−1 多项式 g ( x ) g(x) g(x),满足 g ( x ) ≡ ln ( f ( x ) ) ( m o d x n ) g(x) \equiv \operatorname{ln}(f(x))~(\bmod x^n) g(x)≡ln(f(x)) (modxn)。
保证有 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1。系数对 998244353 998244353 998244353 取模。
看到这里,大多数读者应该都是一脸懵:怎么对一个多项式求自然对数啊?
不要急,还记得前置知识吗?求导和积分。
前面还提到了一句话:把一些式子通过求导变成方便计算的形式,然后再用积分求出答案。
那么,做法已经呼之欲出了:
对 g ( x ) g(x) g(x) 求导有: g ′ ( x ) ≡ ln ′ ( f ( x ) ) ∗ f ′ ( x ) ≡ f ′ ( x ) f ( x ) ( m o d x n ) g'(x) \equiv \operatorname{ln}'(f(x)) * f'(x) \equiv \frac{f'(x)}{f(x)}~(\bmod~x^n) g′(x)≡ln′(f(x))∗f′(x)≡f(x)f′(x) (mod xn)。
这不就是求一个导数和一个逆元吗?
最后积分回去就好啦。
对于常数项,显然有 e 0 = 1 e^0 = 1 e0=1,即 ln 1 = 0 \ln 1 = 0 ln1=0,所以保证了 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1,否则是很难做的。
时间复杂度 O ( n log 2 n ) \mathcal{O}(n \log_2n) O(nlog2n)。
贴上代码:
inline void Get_Ln(int *f, int *g, int n)
{
Get_Inverse_Element(f, g, n);
memset(t, 0, sizeof(int) * p);
Get_Derivative(f, t, n);
NTT(g, p), NTT(t, p);
for(register int i = 0; i < p; i++) g[i] = 1LL * g[i] * t[i] % MOD;
return NTT(g, p, true), Get_Integral(g, g, n), void();
}
多项式牛顿迭代
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x),求出一个度数同为 n − 1 n - 1 n−1 多项式 g ( x ) g(x) g(x),满足 f ( g ( x ) ) ≡ 0 ( m o d x n ) f(g(x)) \equiv 0~(\bmod x^n) f(g(x))≡0 (modxn)。
保证有 [ x 0 ] f ( x ) = 0 [x^0]f(x) = 0 [x0]f(x)=0。系数对 998244353 998244353 998244353 取模。
首先需要知道前置知识:泰勒展开。
对于多项式
f
(
x
)
f(x)
f(x),如果我们已经知道了
f
(
x
0
)
f(x_0)
f(x0) 的值,那么
f
(
x
)
f(x)
f(x) 的值可以用如下方法表示:
f
(
x
)
=
∑
i
=
0
∞
(
f
(
i
)
(
x
0
)
i
!
∗
(
x
−
x
0
)
i
)
=
f
(
x
0
)
+
∑
i
=
1
∞
(
f
(
i
)
(
x
0
)
i
!
∗
(
x
−
x
0
)
i
)
f(x) = \sum_{i = 0}^{\infin} (\frac{f^{(i)}(x_0)}{i!} * (x - x_0)^i) = f(x_0) + \sum_{i = 1}^{\infin} (\frac{f^{(i)}(x_0)}{i!} * (x - x_0)^i)
f(x)=i=0∑∞(i!f(i)(x0)∗(x−x0)i)=f(x0)+i=1∑∞(i!f(i)(x0)∗(x−x0)i)
其中
f
(
i
)
(
x
0
)
f^{(i)}(x_0)
f(i)(x0) 指的是多项式
f
(
x
)
f(x)
f(x) 在
x
0
x_0
x0 处的
i
i
i 阶导数,简单来讲就是对它求
i
i
i 次导数得出的值。
这个公式的证明和其它用法大可不必知道,我们只会用它做一些最基本的操作,不会超出我们的认知范围。
还是考虑倍增。
对于 n = 1 n = 1 n=1 的情况,此时我们一般会默认 [ x 0 ] g ( x ) = 1 [x^0]g(x) = 1 [x0]g(x)=1。
设求出了一个多项式 t ( x ) t(x) t(x),满足 f ( t ( x ) ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) f(t(x)) \equiv 0~(\bmod~x^{\lceil\frac{n}{2}\rceil}) f(t(x))≡0 (mod x⌈2n⌉)。
那么我们要求多项式 g ( x ) g(x) g(x),满足 f ( g ( x ) ) ≡ 0 ( m o d x n ) f(g(x)) \equiv 0~(\bmod~x^{n}) f(g(x))≡0 (mod xn)。
又可以得出: f ( g ( x ) ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) f(g(x)) \equiv 0~(\bmod~x^{\lceil\frac{n}{2}\rceil}) f(g(x))≡0 (mod x⌈2n⌉)。
如果您阅读完了上面的《拉格朗日插值》一节,就一定可以知道这个定理:
对于多项式 f ( x ) f(x) f(x),必然满足 f ( x ) ≡ f ( a ) ( m o d ( x − a ) ) f(x) \equiv f(a)~(\bmod (x - a)) f(x)≡f(a) (mod(x−a))。
那么我们很快可以得出结论: g ( x ) − t ( x ) ≡ 0 ( m o d x ⌈ n 2 ⌉ ) g(x) - t(x) \equiv 0~(\bmod~x^{\lceil\frac{n}{2}\rceil}) g(x)−t(x)≡0 (mod x⌈2n⌉)。
而这个结论有什么作用呢?往下看。
我们要求的是 g ( x ) g(x) g(x),满足 f ( g ( x ) ) ≡ 0 ( m o d x n ) f(g(x)) \equiv 0~(\bmod~x^{n}) f(g(x))≡0 (mod xn)。
那么我们在 t ( x ) t(x) t(x) 处进行泰勒展开,得到:
f ( g ( x ) ) ≡ f ( t ( x ) ) + ∑ i = 1 ∞ ( f ( i ) ( t ( x ) ) i ! ∗ ( g ( x ) − t ( x ) ) i ) ≡ 0 ( m o d x n ) f(g(x)) \equiv f(t(x)) + \sum_{i = 1}^{\infin} (\frac{f^{(i)}(t(x))}{i!} * (g(x) - t(x))^i) \equiv 0~(\bmod ~x^{n}) f(g(x))≡f(t(x))+i=1∑∞(i!f(i)(t(x))∗(g(x)−t(x))i)≡0 (mod xn)
又由于上面的结论,我们可以发现,在 ( g ( x ) − t ( x ) ) i (g(x) - t(x))^i (g(x)−t(x))i 这个式子中,当 i ≥ 2 i\ge 2 i≥2 时,前 n n n 位必然为 0 0 0。
所以整个式子就等于:
f
(
g
(
x
)
)
≡
f
(
t
(
x
)
)
+
f
′
(
t
(
x
)
)
∗
(
g
(
x
)
−
t
(
x
)
)
≡
0
(
m
o
d
x
n
)
f(g(x)) \equiv f(t(x)) + f'(t(x)) * (g(x) - t(x)) \equiv 0~(\bmod~x^{n})
f(g(x))≡f(t(x))+f′(t(x))∗(g(x)−t(x))≡0 (mod xn)
稍加推导一下就有:
g
(
x
)
=
t
(
x
)
−
f
(
t
(
x
)
)
f
′
(
t
(
x
)
)
(
m
o
d
x
n
)
g(x) = t(x) - \frac{f(t(x))}{f'(t(x))}~(\bmod~x^{n})
g(x)=t(x)−f′(t(x))f(t(x)) (mod xn)
这样就可以倍增求出
g
(
x
)
g(x)
g(x) 了。时间复杂度
O
(
n
log
2
n
)
\mathcal{O}(n \log_2n)
O(nlog2n)。
多项式指数函数
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x),求出一个度数同为 n − 1 n - 1 n−1 多项式 g ( x ) g(x) g(x),满足 g ( x ) ≡ e f ( x ) ( m o d x n ) g(x) \equiv e^{f(x)}~(\bmod x^n) g(x)≡ef(x) (modxn)。
保证有 [ x 0 ] f ( x ) = 0 [x^0]f(x) = 0 [x0]f(x)=0。系数对 998244353 998244353 998244353 取模。
显然把 e f ( x ) e^{f(x)} ef(x) 求导不会有任何变化。
所以,我们考虑用刚刚学到的牛顿迭代法做这道题。
我们构造出一个多项式(不管它的实际系数) h ( g ( x ) ) ≡ ln ( g ( x ) ) − f ( x ) ( m o d x n ) h(g(x)) \equiv \operatorname{ln}(g(x)) - f(x)~(\bmod~x^n) h(g(x))≡ln(g(x))−f(x) (mod xn)。
那么把
g
(
x
)
≡
e
f
(
x
)
(
m
o
d
x
n
)
g(x) \equiv e^{f(x)}~(\bmod x^n)
g(x)≡ef(x) (modxn) 代入就有:
h
(
g
(
x
)
)
≡
ln
(
g
(
x
)
)
−
f
(
x
)
≡
ln
(
e
f
(
x
)
)
−
f
(
x
)
≡
f
(
x
)
−
f
(
x
)
≡
0
(
m
o
d
x
n
)
h(g(x)) \equiv \operatorname{ln}(g(x)) - f(x)\equiv\operatorname{ln}(e^{f(x)}) - f(x) \equiv f(x) - f(x)\equiv0~(\bmod~x^n)
h(g(x))≡ln(g(x))−f(x)≡ln(ef(x))−f(x)≡f(x)−f(x)≡0 (mod xn)
然后我们就可以套用上面的多项式牛顿迭代了:
g
(
x
)
≡
t
(
x
)
−
ln
(
t
(
x
)
)
−
f
(
x
)
1
t
(
x
)
≡
t
(
x
)
−
t
(
x
)
∗
(
ln
(
t
(
x
)
)
−
f
(
x
)
)
(
m
o
d
x
n
)
g(x) \equiv t(x) - \frac{\operatorname{ln}(t(x))-f(x)}{\frac{1}{t(x)}}\equiv t(x) - t(x) * (\operatorname{ln}(t(x)) - f(x))~(\bmod~x^{n})
g(x)≡t(x)−t(x)1ln(t(x))−f(x)≡t(x)−t(x)∗(ln(t(x))−f(x)) (mod xn)
注意一点:
f
(
x
)
f(x)
f(x) 在求导的时候是被看作常数的。
对于常数项,可以参考上文《多项式对数函数》的证明。
时间复杂度 O ( n log 2 n ) \mathcal{O}(n \log_2n) O(nlog2n)。
代码:
inline void Get_Exp(int *f, int *g, int n)
{
Get_Operation_Sequence(n, seq[2]);
for(register int k = seq[2][0]; k >= 1; k--)
{
int m = seq[2][k];
if(m == 1)
{
g[0] = 1;
continue;
}
Get_Ln(g, h, m);
Init(m << 1);
fill(h + m, h + p, 0);
memcpy(s, f, sizeof(int) * m);
fill(s + m, s + p, 0);
NTT(g, p), NTT(h, p), NTT(s, p);
for(register int i = 0; i < p; i++) g[i] = 1LL * g[i] * Get_MOD(Get_MOD(1LL - h[i] + MOD) + s[i]) % MOD;
NTT(g, p, true), fill(g + m, g + p, 0), memset(h, 0, sizeof(int) * p), void();
}
}
学完了这里,您可以尝试用其去推一推多项式求逆和多项式开根的式子,可以发现和原来推出来的式子是一样的。
多项式快速幂
给出一个度数为 n − 1 n - 1 n−1 的多项式 f ( x ) f(x) f(x) 和一个正整数 k k k,其中 k ≤ 1 0 1 0 5 k \le 10^{10^5} k≤10105,求出一个度数同为 n − 1 n - 1 n−1 多项式 g ( x ) g(x) g(x),满足 g ( x ) ≡ e f ( x ) ( m o d x n ) g(x) \equiv e^{f(x)}~(\bmod x^n) g(x)≡ef(x) (modxn)。
保证有 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1。系数对 998244353 998244353 998244353 取模。
如果刷高中数学题刷得有感觉,应该可以一眼看出这里可以求 ln \ln ln 后求 exp \exp exp。
所以,我们求出 t ( x ) = ln ( f ( x ) ) t(x) = \ln(f(x)) t(x)=ln(f(x)),那么有 ln ( g ( x ) ) = k ∗ t ( x ) \ln(g(x)) = k * t(x) ln(g(x))=k∗t(x)。
于是我们把 t ( x ) t(x) t(x) 的每一项的系数乘上一个 k k k,最后对 t ( x ) t(x) t(x) 求 exp \exp exp,得到的即为 g ( x ) g(x) g(x)。
时间复杂度 O ( n log 2 n ) \mathcal{O}(n \log_2n) O(nlog2n)。
不过在贴代码前,有一个地方需要思考: k k k 在读入时应该对什么取模?
可能会有人脱口而出是 φ ( 998244353 ) \varphi(998244353) φ(998244353)。但是,需要郑重声明的是:欧拉定理在多项式的运算意义下是不成立的,正确的模数应当为 998244353 998244353 998244353 本身(阅读完下文后,可以发现这也是规定了 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1 的原因)。
证明:
我们考虑证明一个更一般的情况: f p ( x ) ≡ f ( x p ) ( m o d p ) f^p(x) \equiv f(x^p)~(\bmod~p) fp(x)≡f(xp) (mod p)。
由二项式定理和 L u c a s Lucas Lucas 定理可以得出: ( a + b ) p ≡ a p + b p ( m o d p ) (a + b)^p \equiv a^p + b^p~(\bmod~p) (a+b)p≡ap+bp (mod p)。
那么由数学归纳法,我们设 f ( x ) f(x) f(x) 度数为 k k k 且 f ( x ) = g ( x ) + a k x k f(x) = g(x) + a_kx^k f(x)=g(x)+akxk,同时上述结论对于度数为 k − 1 k - 1 k−1 的多项式成立。
则有 f p ( x ) ≡ g p ( x ) + a k p x k p ≡ g ( x p ) + a k x k p ≡ f ( x p ) ( m o d p ) f^p(x) \equiv g^p(x) + a_k^px^{kp} \equiv g(x^p) + a_kx^{kp}\equiv f(x^p)~(\bmod~p) fp(x)≡gp(x)+akpxkp≡g(xp)+akxkp≡f(xp) (mod p)。(这里可以使用欧拉定理)
在这道题中,由于 n < p n < p n<p,所以在模 p p p 意义下有 f p ( x ) ≡ f ( x p ) ( m o d x n ) f^p(x)\equiv f(x^p) ~(\bmod~x^n) fp(x)≡f(xp) (mod xn)。
由因为 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1,所以在模 p p p 意义下 f p ( x ) ≡ 1 ( m o d x n ) f^p(x) \equiv 1~(\bmod x^n) fp(x)≡1 (modxn)。
代码:
inline int Read_Get_MOD()
{
int res = 0; char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c))
{
res = (10LL * res + (c - '0')) % MOD;
c = getchar();
}
return res;
}
inline void Get_Quick_Pow(int *f, int *g, int n, int k)
{
Get_Ln(f, r, n);
for(register int i = 0; i < p; i++) r[i] = 1LL * r[i] * k % MOD;
return Get_Exp(r, g, n);
}
加强版不保证 [ x 0 ] f ( x ) = 1 [x^0]f(x) = 1 [x0]f(x)=1?
我们如果把所有数除去常数项,就可以仿照上述方法了。当然,不要忘记在最后把这个除掉的数乘回去(注意这个数是整数,根据费马小定理,它的指数需要对 φ ( 998244353 ) \varphi(998244353) φ(998244353) 取模)。
不过,当然会存在常数项为
0
0
0 的情况。这也很好解决,我们只要找到第一个
a
t
a_t
at(系数)不为
0
0
0 的
t
t
t,就可以套用如下公式:
f
k
(
x
)
=
x
t
k
∗
(
f
(
x
)
x
t
)
k
f^k(x) = x^{tk} * (\frac{f(x)}{x^t})^k
fk(x)=xtk∗(xtf(x))k
从本质上讲,这个操作就是降次之后升次。
注意这个式子中的 k k k 还是对 998244353 998244353 998244353 取模。
贴上代码:(由于常数过大,不吸氧只有 80 80 80 分,不过笔者实在是卡不动了。。。)
inline void Read_Get_MOD(int &k1, int &k2, int &k3)
{
k1 = k2 = k3 = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c))
{
k1 = (10LL * k1 + (c - '0')) % MOD;
k2 = (10LL * k2 + (c - '0')) % (MOD - 1);
if(k3 < n) k3 = 10 * k3 + (c - '0');
c = getchar();
}
return;
}
inline void Get_Quick_Pow(int *f, int *g, int n, int k1, int k2)
{
int st = 0;
while(st < n && f[st] == 0) ++st;
if(st == n || 1LL * st * k1 > n)
{
for(register int i = 0; i < n; i++) putchar('0'), putchar(' ');
putchar('\n');
return;
}
int inv = Pow(f[st], MOD - 2), mul = Pow(f[st], k2);
for(register int i = 0; i < n; i++) f[i] = 1LL * f[i + st] * inv % MOD;
Get_Ln(f, r, n);
for(register int i = 0; i < p; i++) r[i] = 1LL * r[i] * k1 % MOD;
Get_Exp(r, g, n);
int tk = st * k1;
for(register int i = 0; i < tk; i++) putchar('0'), putchar(' ');
for(register int i = tk; i < n; i++) printf("%lld ", 1LL * g[i - tk] * mul % MOD);
putchar('\n');
return;
}
inline void Run()
{
int k1, k2, k3;
scanf("%d", &n);
Read_Get_MOD(k1, k2, k3);
for(register int i = 0; i < n; i++) scanf("%d ", f + i);
if(f[0] == 0 && k3 >= n)
{
for(register int i = 0; i < n; i++) putchar('0'), putchar(' ');
putchar('\n');
return;
}
Get_Quick_Pow(f, g, n, k1, k2);
return;
}
最后,附上这篇文章的总代码。
除快速幂的加强版需要吸氧之外,上文提到的所有模板题均可通过,仅需改写 Run()函数即可。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<cctype>
#include<ctime>
#include<algorithm>
using namespace std;
struct Complex
{
double r, i;
Complex(double _r = 0.0, double _i = 0.0) : r(_r), i(_i) {}
Complex operator + (const Complex &t) const { return Complex(r + t.r, i + t.i); }
Complex operator - (const Complex &t) const { return Complex(r - t.r, i - t.i); }
Complex operator * (const Complex &t) const { return Complex(r * t.r - i * t.i, r * t.i + i * t.r); }
};
namespace Quadratic_Residue
{
#define MOD 998244353
int si;
struct Complex_MOD
{
int a, b;
Complex_MOD operator * (const Complex_MOD &t) const
{
Complex_MOD res;
res.a = (1LL * a * t.a % MOD + 1LL * b * t.b % MOD * si % MOD) % MOD;
res.b = (1LL * a * t.b % MOD + 1LL * b * t.a % MOD) % MOD;
return res;
}
};
inline int Pow_Real(int a, int b)
{
int res = 1;
while(b)
{
if(b & 1) res = 1LL * res * a % MOD;
a = 1LL * a * a % MOD;
b >>= 1;
}
return res;
}
inline int Pow_Complex(Complex_MOD val, int t)
{
Complex_MOD res;
res.a = 1, res.b = 0;
while(t)
{
if(t & 1) res = res * val;
val = val * val;
t >>= 1;
}
return res.a;
}
inline int Solve(int n)
{
if(n == 0) return 0;
srand((unsigned)(time(NULL)));
int p1, p2;
while(true)
{
p1 = 1LL * rand() * rand() % MOD;
p2 = (1LL * p1 * p1 % MOD - n + MOD) % MOD;
if(Pow_Real(p2, (MOD - 1) / 2) != 1) break;
}
si = p2;
Complex_MOD val;
val.a = p1, val.b = 1;
int ans = Pow_Complex(val, (MOD + 1) / 2);
if(MOD - ans < ans) ans = MOD - ans;
return ans;
}
#undef MOD
}
namespace Polynomial
{
#define MAXN 5000005
#define MOD 998244353
#define Min_G 3
#define Inv_Min_G 332748118
int n, f[MAXN], g[MAXN], t[MAXN], h[MAXN], s[MAXN], r[MAXN], u[MAXN];
int p, q, rev[MAXN], seq[3][MAXN];
const double PI = acos(-1.0);
inline int Get_MOD(int k) { if(k >= MOD) k -= MOD; return k; }
inline void Read_Get_MOD(int &k1, int &k2, int &k3)
{
k1 = k2 = k3 = 0;
char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c))
{
k1 = (10LL * k1 + (c - '0')) % MOD;
k2 = (10LL * k2 + (c - '0')) % (MOD - 1);
if(k3 < n) k3 = 10 * k3 + (c - '0');
c = getchar();
}
return;
}
inline int Pow(int a, int b)
{
int res = 1;
while(b)
{
if(b & 1) res = 1LL * res * a % MOD;
a = 1LL * a * a % MOD;
b >>= 1;
}
return res;
}
inline void Init(int n)
{
p = 1, q = -1;
while(p < n) p <<= 1, ++q;
for(register int i = 0; i < p; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << q);
return;
}
inline void Get_Operation_Sequence(int n, int *seq)
{
seq[0] = 0;
while(n != 1)
{
seq[++seq[0]] = n;
n = (n + 1) >> 1;
}
seq[++seq[0]] = 1;
return;
}
inline void Get_Derivative(int *f, int *g, int n)
{
for(register int i = 1; i < n; i++)
g[i - 1] = 1LL * i * f[i] % MOD;
return g[n - 1] = 0, void();
}
inline void Get_Integral(int *f, int *g, int n)
{
for(register int i = n - 1; i >= 1; i--)
g[i] = 1LL * f[i - 1] * Pow(i, MOD - 2) % MOD;
return g[0] = 0, void();
}
inline void FFT(Complex *f, int n, double t = 1.0)
{
for(register int i = 0; i < n; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
for(register int len = 2; len <= n; len <<= 1)
{
int k = len >> 1;
Complex _w(cos(PI / k), t * sin(PI / k));
for(register int i = 0; i < n; i += len)
{
Complex w(1.0, 0.0);
for(register int j = 0; j < k; j++)
{
Complex u = f[i + j], v = w * f[i + j + k];
f[i + j] = u + v;
f[i + j + k] = u - v;
w = w * _w;
}
}
}
if(t == -1) for(register int i = 0; i < n; i++) f[i].r /= n, f[i].i /= n;
return;
}
inline void NTT(int *f, int n, bool Inverse = false)
{
for(register int i = 0; i < n; i++) if(i < rev[i]) swap(f[i], f[rev[i]]);
for(register int len = 2; len <= n; len <<= 1)
{
int k = (len >> 1), g = Pow(Inverse ? Inv_Min_G : Min_G, (MOD - 1) / len);
for(register int i = 0; i < n; i += len)
{
int w = 1;
for(register int j = 0; j < k; j++)
{
int u = f[i + j], v = 1LL * w * f[i + j + k] % MOD;
f[i + j] = Get_MOD(u + v);
f[i + j + k] = Get_MOD(u - v + MOD);
w = 1LL * w * g % MOD;
}
}
}
if(Inverse)
{
int Inv_N = Pow(n, MOD - 2);
for(register int i = 0; i < n; i++) f[i] = 1LL * f[i] * Inv_N % MOD;
}
return;
}
inline void Get_Inverse_Element(int *f, int *g, int n)
{
Get_Operation_Sequence(n, seq[0]);
for(register int k = seq[0][0]; k >= 1; k--)
{
int m = seq[0][k];
if(m == 1)
{
g[0] = Pow(f[0], MOD - 2);
continue;
}
Init(m << 1);
memcpy(t, f, sizeof(int) * m);
fill(t + m, t + p, 0);
NTT(t, p), NTT(g, p);
for(register int i = 0; i < p; i++) g[i] = 1LL * g[i] * Get_MOD(2LL - 1LL * t[i] * g[i] % MOD + MOD) % MOD;
NTT(g, p, true), fill(g + m, g + p, 0);
continue;
}
}
inline void Multiply(int *f, int n, int *g, int m, int *v)
{
Init(n + m - 1);
memcpy(h, f, sizeof(int) * n), fill(h + n, h + p, 0);
memcpy(s, g, sizeof(int) * m), fill(s + m, s + p, 0);
NTT(h, p), NTT(s, p);
for(register int i = 0; i < p; i++) v[i] = 1LL * h[i] * s[i] % MOD;
return NTT(v, p, true), fill(v + n + m - 1, v + p, 0), void();
}
inline void Divide(int *f, int n, int *g, int m, int *v)
{
memcpy(s, g, sizeof(int) * m);
reverse(s, s + m);
Get_Inverse_Element(s, r, n - m + 1);
memcpy(t, f, sizeof(int) * n);
reverse(t, t + n);
Multiply(t, n, r, n - m + 1, v);
return reverse(v, v + n - m + 1);
}
inline void Get_Remainder(int *f, int n, int *g, int m, int *v, int *x)
{
Multiply(g, m, v, n - m + 1, x);
for(register int i = 0; i < m; i++) x[i] = Get_MOD(f[i] - x[i] + MOD);
return;
}
inline void Get_Sqrt(int *f, int *g, int n)
{
Get_Operation_Sequence(n, seq[1]);
for(register int k = seq[1][0]; k >= 1; k--)
{
int m = seq[1][k];
if(m == 1)
{
g[0] = Quadratic_Residue :: Solve(f[0]);
continue;
}
Init(m << 1);
memset(h, 0, sizeof(int) * p);
Get_Inverse_Element(g, h, m);
memcpy(s, f, sizeof(int) * m);
fill(s + m, s + p, 0);
NTT(g, p), NTT(h, p), NTT(s, p);
int Inv2 = Pow(2, MOD - 2);
for(register int i = 0; i < p; i++) g[i] = 1LL * Inv2 * Get_MOD(g[i] + 1LL * h[i] * s[i] % MOD) % MOD;
NTT(g, p, true), fill(g + m, g + p, 0);
continue;
}
return;
}
inline void Get_Ln(int *f, int *g, int n)
{
Get_Inverse_Element(f, g, n);
memset(t, 0, sizeof(int) * p);
Get_Derivative(f, t, n);
NTT(g, p), NTT(t, p);
for(register int i = 0; i < p; i++) g[i] = 1LL * g[i] * t[i] % MOD;
return NTT(g, p, true), Get_Integral(g, g, n), void();
}
inline void Get_Exp(int *f, int *g, int n)
{
Get_Operation_Sequence(n, seq[2]);
for(register int k = seq[2][0]; k >= 1; k--)
{
int m = seq[2][k];
if(m == 1)
{
g[0] = 1;
continue;
}
Get_Ln(g, h, m);
Init(m << 1);
fill(h + m, h + p, 0);
memcpy(s, f, sizeof(int) * m);
fill(s + m, s + p, 0);
NTT(g, p), NTT(h, p), NTT(s, p);
for(register int i = 0; i < p; i++) g[i] = 1LL * g[i] * Get_MOD(Get_MOD(1LL - h[i] + MOD) + s[i]) % MOD;
NTT(g, p, true), fill(g + m, g + p, 0), memset(h, 0, sizeof(int) * p), void();
}
}
inline void Get_Quick_Pow(int *f, int *g, int n, int k1, int k2)
{
int st = 0;
while(st < n && f[st] == 0) ++st;
if(st == n || 1LL * st * k1 > n)
{
for(register int i = 0; i < n; i++) putchar('0'), putchar(' ');
putchar('\n');
return;
}
int inv = Pow(f[st], MOD - 2), mul = Pow(f[st], k2);
for(register int i = 0; i < n; i++) f[i] = 1LL * f[i + st] * inv % MOD;
Get_Ln(f, r, n);
for(register int i = 0; i < p; i++) r[i] = 1LL * r[i] * k1 % MOD;
Get_Exp(r, g, n);
int tk = st * k1;
for(register int i = 0; i < tk; i++) putchar('0'), putchar(' ');
for(register int i = tk; i < n; i++) printf("%lld ", 1LL * g[i - tk] * mul % MOD);
putchar('\n');
return;
}
inline void Run()
{
/*
* You wanna do something here?
*/
}
#undef MAXN
#undef MOD
#undef Min_G
#undef Inv_Min_G
}
int main()
{
Polynomial :: Run();
return 0;
}
1405



被折叠的 条评论
为什么被折叠?



