超全的多项式全家桶(❁´◡`❁)
感谢Meteorshower-Y的大力支持和指导, 这是大佬的blog !!ヾ(≧▽≦*)o
多项式学习笔记
快速傅里叶变换(FFT)
快速傅里叶变换(FFT) ,主要用于加速多项式乘法,对于两个多项式A A A 和B B B , FFT可以将朴素的O ( n 2 ) O(n^2) O ( n 2 ) 优化为O ( n log n ) O(n \log n) O ( n log n ) 。
单位元
先看一下单位元的几个性质,在接下来的算法中有很大的用途。
ω n k = e 2 π i k n \omega_n ^ k = e ^{\frac{2\pi i k}{n}} ω n k = e n 2 π i k
ω d n d k = ω n k \omega_{dn} ^ {dk} = \omega_n^k ω d n d k = ω n k
ω n k = a + b i , ω n − k = a − b i \omega_n^k = a + bi, \omega_n^{-k} = a - bi ω n k = a + b i , ω n − k = a − b i
ω n k + n 2 = − ω n k \omega _n ^{k + \frac{n}{2}} = - \omega_n^k ω n k + 2 n = − ω n k
以上变换均可由欧拉公式e i θ = cos θ + i sin θ e^{i \theta} = \cos \theta + i\sin \theta e i θ = cos θ + i sin θ 推得。
离散傅里叶变换(DFT)
离散傅里叶变换(DFT) 主要是利用分治思想,根据一个n n n 次的多项式可以由n + 1 n + 1 n + 1 个点唯一确定,
首先将多项式
A ( x ) = ∑ i = 0 n a i x i A(x) = \sum_{i=0} ^n a_i x^i
A ( x ) = i = 0 ∑ n a i x i
其系数进行奇偶性分类,得到,
A 0 ( x ) = a 0 + a 2 x 1 + a 4 x 2 + ⋯ A 1 ( x ) = a 1 + a 3 x 1 + a 5 x 2 + ⋯ A_0(x)= a_0+a_2 x^1 +a_4 x^2 + \cdots \\
A_1(x)= a_1+a_3 x^1 +a_5 x^2 + \cdots \\
A 0 ( x ) = a 0 + a 2 x 1 + a 4 x 2 + ⋯ A 1 ( x ) = a 1 + a 3 x 1 + a 5 x 2 + ⋯
所以我们可以表示为 :
A ( x ) = A 0 ( x 2 ) + x ⋅ A 1 ( x 2 ) A(x) = A_0 (x^2) +x \cdot A_1(x^2)
A ( x ) = A 0 ( x 2 ) + x ⋅ A 1 ( x 2 )
将 ω n k \omega_n^k ω n k 与 ω n k + n 2 \omega_n^{k+ \frac{n}{2}} ω n k + 2 n 代入得:
{ A ( ω n k ) = A 0 ( ω n 2 k ) + ω n k A 1 ( ω n 2 k ) A ( ω n k + n 2 ) = A 0 ( ω n 2 k ) − ω n k A 1 ( ω n 2 k ) \left\{
\begin{aligned}
&A(\omega_n^k) = A_0(\omega_n^{2k})+\omega_n^k A_1(\omega_n^{2k}) \\
&A(\omega_n^{k+ \frac{n}{2}}) = A_0(\omega_n^{2k})-\omega_n^k A_1(\omega_n^{2k}) \\
\end{aligned}
\right.
{ A ( ω n k ) = A 0 ( ω n 2 k ) + ω n k A 1 ( ω n 2 k ) A ( ω n k + 2 n ) = A 0 ( ω n 2 k ) − ω n k A 1 ( ω n 2 k )
同时我们可以发现两个式子只有常数不一样,递归计算即可。
时间复杂度O ( n log n ) O(n \log n) O ( n log n ) 。
在这里我们将系数变成了点值。
离散傅里叶逆变换(IDFT)
离散傅里叶逆变换(IDFT) ,可以将点值快速转化为系数,从而得出结果多项式。
需要用到单位根反演:
1 n ∑ i = 0 n − 1 ω n x ∗ i = [ x m o d n = 0 ] \frac{1}{n} \sum_{i=0}^{n-1} \omega_n^{x \ast i} = [x \bmod n =0]
n 1 i = 0 ∑ n − 1 ω n x ∗ i = [ x m o d n = 0 ]
证明 :
由于 ω n x ∗ i = ω n x ∗ ( i − 1 ) ∗ ω n x \omega_n ^ {x \ast i} = \omega_n^ {x \ast (i-1)} \ast \omega_n^x ω n x ∗ i = ω n x ∗ ( i − 1 ) ∗ ω n x
所以ω n x ∗ i \omega _n ^{x\ast i} ω n x ∗ i 为等比数列,
∴ 1 n ∑ i = 0 n − 1 ω n x ∗ i = { 1 n ∑ i = 0 n − 1 1 i = n n = 1 x m o d n = 0 1 n ⋅ 1 − ω n n ∗ x 1 − ω n x = 1 n ⋅ 1 − 1 x 1 − ω n x = 0 x m o d n ≠ 0 \therefore
\frac{1}{n} \sum_{i=0}^{n-1} \omega_n^{x \ast i}=
\left\{
\begin{aligned}
&\frac{1}{n} \sum_{i=0}^{n-1} 1^i = \frac{n}{n} = 1 & x \bmod n=0\\
&\frac{1}{n} \cdot \frac{1- \omega _n ^ {n \ast x}}{1-\omega _n ^ x} = \frac{1}{n} \cdot \frac{1-1^x}{1-\omega_n^x} =0 & x\bmod n \ne 0
\end{aligned}
\right.
∴ n 1 i = 0 ∑ n − 1 ω n x ∗ i = ⎩ ⎪ ⎪ ⎪ ⎪ ⎪ ⎨ ⎪ ⎪ ⎪ ⎪ ⎪ ⎧ n 1 i = 0 ∑ n − 1 1 i = n n = 1 n 1 ⋅ 1 − ω n x 1 − ω n n ∗ x = n 1 ⋅ 1 − ω n x 1 − 1 x = 0 x m o d n = 0 x m o d n = 0
证明
设 c = a ∗ b c i = ∑ j = 0 i a j ⋅ b i − j = ∑ p = 0 ∑ q = 0 a p ⋅ b q [ ( p + q ) m o d n = 0 ] n c i = ∑ p = 0 ∑ q = 0 a p ⋅ b q ∑ j = 0 ω n ( p + q − i ) j = ∑ j = 0 ω n ( − i ) j ( ∑ p = 0 ω n p j a p ) ( ∑ q = 0 ω n q j b q ) 设 f a ( j ) = ∑ i = 0 ω n i j a i , f a − 1 ( j ) = ∑ i = 0 ω n ( − i ) j a i n c i = ∑ j = 0 ω n ( − i ) j f a ( j ) f b ( j ) = ∑ j = 0 ω n ( − i ) j f c ( j ) = f f c − 1 ( i ) 设 c= a\ast b \\
\begin{aligned}
c_i
&= \sum_{j=0}^i a_j \cdot b_{i-j} \\
&=\sum_{p=0}\sum_{q=0} a_p \cdot b_q [(p+q) \bmod n=0] \\
nc_i
&= \sum_{p=0}\sum_{q=0} a_p \cdot b_q \sum_{j=0} \omega_n^{(p+q-i)j}\\
&= \sum_{j=0}\omega_n^{(-i)j} \bigg( \sum_{p=0} \omega_n^{pj} a_p\bigg) \bigg( \sum_{q=0} \omega_n^{qj} b_q\bigg)
\end{aligned} \\
设 f_a(j) = \sum_{i=0} \omega_n^{ij} a_i , f_a^{-1}(j) =\sum_{i=0} \omega_n^{(-i)j} a_i \\
\begin{aligned}
nc_i
&= \sum_{j=0} \omega_n^{(-i)j}f_a(j)f_b(j) \\
&= \sum_{j=0} \omega_n^{(-i)j}f_c(j) \\
&= f_{f_c}^{-1} (i)
\end{aligned}
设 c = a ∗ b c i n c i = j = 0 ∑ i a j ⋅ b i − j = p = 0 ∑ q = 0 ∑ a p ⋅ b q [ ( p + q ) m o d n = 0 ] = p = 0 ∑ q = 0 ∑ a p ⋅ b q j = 0 ∑ ω n ( p + q − i ) j = j = 0 ∑ ω n ( − i ) j ( p = 0 ∑ ω n p j a p ) ( q = 0 ∑ ω n q j b q ) 设 f a ( j ) = i = 0 ∑ ω n i j a i , f a − 1 ( j ) = i = 0 ∑ ω n ( − i ) j a i n c i = j = 0 ∑ ω n ( − i ) j f a ( j ) f b ( j ) = j = 0 ∑ ω n ( − i ) j f c ( j ) = f f c − 1 ( i )
因为 f a f_a f a 就是 a a a 在 DFT 后的结果,所以f a − 1 f_a^{-1} f a − 1 就是 对应的IDFT,最后除以对应长度 n n n ,即为所求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <bits/stdc++.h> using namespace std;const int N = 4e6 + 10 ;const double pi = acos (-1.0 );int n, m;struct Complex { double a, b; Complex (double x = 0 , double y = 0 ) : a (x), b (y) {} friend Complex operator + (Complex x, Complex y) {return Complex (x.a + y.a, x.b + y.b);} friend Complex operator - (Complex x, Complex y) {return Complex (x.a - y.a, x.b - y.b);} friend Complex operator * (Complex x, Complex y) {return Complex (x.a * y.a - x.b * y.b, x.b * y.a + y.b * x.a);} }; int recover[N];Complex F[N], G[N], H[N]; void FFT (Complex *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { Complex x (cos(pi / k), type * sin(pi / k)) ; for (int i = 0 ; i < len; i += (k << 1 )) { Complex w (1 , 0 ) ; for (int j = 0 ; j < k; j++) { Complex y = a[i + j]; Complex z = w * a[i + j + k]; a[i + j] = y + z; a[i + j + k] = y - z; w = w * x; } } } if (type == -1 ) for (int i = 0 ; i < len; i++) a[i].a /= len; } int main () { scanf ("%d%d" , &n, &m); for (int i = 0 ; i <= n; i++) scanf ("%lf" , &F[i].a); for (int i = 0 ; i <= m; i++) scanf ("%lf" , &G[i].a); int len = 1 , cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt++; for (int i = 0 ; i <= len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); FFT (F, len, 1 ), FFT (G, len, 1 ); for (int i = 0 ; i <= len; i++) H[i] = F[i] * G[i]; FFT (H, len, -1 ); for (int i = 0 ; i <= n + m; i++) printf ("%d " , (int )(H[i].a + 0.5 )); return 0 ; }
快速数论变换(NTT)
快速数论变换 (NTT)相比于FFT虽然时间复杂度均为O ( n log n ) O(n\log n) O ( n log n ) ,但是FFT的精度却难以保证,并且常数很大, 所以有时NTT才是更好的选择。
原根
原根 定义为:设m m m 为正整数,a a a 是整数,若a m o d m a \bmod m a m o d m 的阶等于φ ( m ) \varphi (m) φ ( m ) ,则称a a a 为 m o d m \bmod m m o d m 的一个原根。
原根有一个很重要的性质可以支持像FFT中单位根一样的运算,即:若P P P 为素数, 假设一个数g g g 是P P P 的原根, 那么g i m o d P g^i \bmod P g i m o d P 的结果两两不同。
可以得到:
ω n ≡ g p − 1 n ( m o d p ) \omega_n \equiv g^{\frac{p - 1}{n}} \pmod p
ω n ≡ g n p − 1 ( m o d p )
然后我们就可以将FFT中的ω n \omega _n ω n 替换为g p − 1 n g^{\frac{p - 1}{n}} g n p − 1
但是注意的是NTT对模数有要求,其模数必须要满足原根的定义,否则是不能使用NTT的,比如998244353 998244353 9 9 8 2 4 4 3 5 3 就为NTT模数, 其原根为3 3 3 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <bits/stdc++.h> using namespace std;#define int long long const int N = 4e6 + 10 ;const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;int n, m;int F[N], G[N], H[N];int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int recover[N];void NTT (int *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi, (mod - 1 ) / (k << 1 )); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j]; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = (y - z + mod) % mod; w = (w * x) % mod; } } } if (type == -1 ) { int inv = qpow (len, mod - 2 ); for (int i = 0 ; i < len; i++) a[i] = a[i] * inv % mod; } } signed main () { scanf ("%lld%lld" , &n, &m); for (int i = 0 ; i <= n; i++) scanf ("%lld" , &F[i]); for (int i = 0 ; i <= m; i++) scanf ("%lld" , &G[i]); int len = 1 , cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); NTT (F, len, 1 ), NTT (G, len, 1 ); for (int i = 0 ; i < len; i++) H[i] = (F[i] * G[i]) % mod; NTT (H, len, -1 ); for (int i = 0 ; i <= n + m; i++) printf ("%lld " , H[i]); return 0 ; }
快速沃尔什变换 (FWT)
给定两个长度为2 n 2 ^ n 2 n 的两个序列A , B A,B A , B , 求序列C C C ,
C i = ∑ j ⊕ k = i A j × B k C_i = \sum_{j \oplus k = i} A_j \times B_k
C i = j ⊕ k = i ∑ A j × B k
其中⊕ \oplus ⊕ 表示位运算与,或,异或。
或运算
首先求序列F W T [ A ] = ∑ i = i ∣ j A j FWT[A] = \sum_{i = i | j} A _ j F W T [ A ] = ∑ i = i ∣ j A j ,来求出满足条件的i i i 的子集,显然会有
C i = ∑ i = j ∣ k A j × B k ⇒ F W T [ C ] = F W T [ A ] × F W T [ B ] C_i = \sum_{i = j | k} A_j \times B_k \Rightarrow FWT[C] = FWT[A] \times FWT[B]
C i = i = j ∣ k ∑ A j × B k ⇒ F W T [ C ] = F W T [ A ] × F W T [ B ]
接下来就是考虑如何进行F W T FWT F W T 运算, 有
F W T [ A ] = m e r g e ( F W T [ A 0 ] , F W T [ A 0 ] + F W T [ A 1 ] ) I F W T [ A ] = m e r g e ( I F W T [ A 0 ] , I F W T [ A 1 ] − F W T [ A 0 ] ) FWT[A] = merge(FWT[A_0], FWT[A_0] + FWT[A_1]) \\
IFWT[A] = merge(IFWT[A_0], IFWT[A_1] - FWT[A_0])
F W T [ A ] = m e r g e ( F W T [ A 0 ] , F W T [ A 0 ] + F W T [ A 1 ] ) I F W T [ A ] = m e r g e ( I F W T [ A 0 ] , I F W T [ A 1 ] − F W T [ A 0 ] )
1 2 3 4 5 6 7 8 9 10 11 12 13 void FWT_or (int *a, int len, int type) { for (int k = 1 ; k < len; k <<= 1 ) { for (int i = 0 ; i < len; i += (k << 1 )) { for (int j = 0 ; j < k; j++) { a[i + j + k] = ((a[i + j + k] + a[i + j] * type + mod) % mod + mod) % mod; } } } }
与运算
同或运算,有
F W T [ A ] = m e r g e ( F W T [ A 0 ] + F W T [ A 1 ] , F W T [ A 1 ] ) I F W T [ A ] = m e r g e ( I F W T [ A 0 ] − I F W T [ A 1 ] , I F W T [ A 1 ] ) FWT[A] = merge(FWT[A_0] + FWT[A_1], FWT[A_1])\\
IFWT[A] = merge(IFWT[A_0] - IFWT[A_1], IFWT[A_1])
F W T [ A ] = m e r g e ( F W T [ A 0 ] + F W T [ A 1 ] , F W T [ A 1 ] ) I F W T [ A ] = m e r g e ( I F W T [ A 0 ] − I F W T [ A 1 ] , I F W T [ A 1 ] )
1 2 3 4 5 6 7 8 9 10 11 12 13 void FWT_and (int *a, int len, int type) { for (int k = 1 ; k < len; k <<= 1 ) { for (int i = 0 ; i < len; i += (k << 1 )) { for (int j = 0 ; j < k; j++) { a[i + j] = ((a[i + j] + a[i + j + k] * type + mod) % mod + mod) % mod; } } } }
异或运算
推导得
F W T [ A ] = m e r g e ( F W T [ A 0 ] + F W T [ A 1 ] , F W T [ A 0 ] − F W T [ A 1 ] ) I F W T [ A ] = m e r g e ( I F W T [ A 0 ] + I F W T [ A 1 ] 2 , I F W T [ A 0 ] − I F W T [ A 1 ] 2 ) FWT[A] = merge(FWT[A_0] + FWT[A_1], FWT[A_0] - FWT[A_1]) \\
IFWT[A] = merge(\frac{IFWT[A_0] + IFWT[A_1]}{2}, \frac{IFWT[A_0] - IFWT[A_1]}{2}) \\
F W T [ A ] = m e r g e ( F W T [ A 0 ] + F W T [ A 1 ] , F W T [ A 0 ] − F W T [ A 1 ] ) I F W T [ A ] = m e r g e ( 2 I F W T [ A 0 ] + I F W T [ A 1 ] , 2 I F W T [ A 0 ] − I F W T [ A 1 ] )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void FWT_xor (int *a, int len, int type) { for (int k = 1 ; k < len; k <<= 1 ) { for (int i = 0 ; i < len; i += (k << 1 )) { for (int j = 0 ; j < k; j++) { int x = a[i + j], y = a[i + j + k]; a[i + j] = ((x + y) % mod * type + mod) % mod; a[i + j + k] = ((x - y + mod) % mod * type + mod) % mod; } } } }
任意模数快速数论变换
普通的NTT对模数是有要求的其必须满足原根的相关定义,模数必须可以写成a ⋅ 2 k + 1 a \cdot 2 ^ k + 1 a ⋅ 2 k + 1 的形式。
比如:
469762049 = 7 × 2 26 + 1 ( g = 3 ) 998244353 = 119 × 2 23 + 1 ( g = 3 ) 1004535809 = 479 × 2 21 + 1 ( g = 3 ) 469762049 = 7 \times 2 ^ {26} + 1 (g = 3) \\
998244353 = 119 \times 2 ^{23} + 1(g = 3) \\
1004535809 = 479 \times 2 ^ {21} + 1(g = 3)
4 6 9 7 6 2 0 4 9 = 7 × 2 2 6 + 1 ( g = 3 ) 9 9 8 2 4 4 3 5 3 = 1 1 9 × 2 2 3 + 1 ( g = 3 ) 1 0 0 4 5 3 5 8 0 9 = 4 7 9 × 2 2 1 + 1 ( g = 3 )
如果题目中模数为1 e 9 + 7 1e9 + 7 1 e 9 + 7 ,那么NTT就会受到限制,然后就可以使用任意模数NTT ,(也可以称为三模数NTT),
计算时可以先找三个大质数, 分别计算结果,然后用中国剩余定理CRT合并即可。
首先记三次NTT的结果为:
a n s ≡ a 1 ( m o d p 1 ) a n s ≡ a 2 ( m o d p 2 ) a n s ≡ a 3 ( m o d p 3 ) ans \equiv a_1 \pmod {p_1} \\
ans \equiv a_2 \pmod {p_2} \\
ans \equiv a_3 \pmod {p_3}
a n s ≡ a 1 ( m o d p 1 ) a n s ≡ a 2 ( m o d p 2 ) a n s ≡ a 3 ( m o d p 3 )
先合并前两个得到:
a n s ≡ a 4 ( m o d p 1 p 2 ) ans \equiv a_4 \pmod {p_1 p_2}
a n s ≡ a 4 ( m o d p 1 p 2 )
将其转化为等式为:
a n s = k p 1 p 2 + a 4 ans = k p_1 p_2 + a_4
a n s = k p 1 p 2 + a 4
接着求k k k :
k = ( a 3 − a 4 ) p 1 − 1 p 2 − 1 ( m o d p 3 ) k = (a_3 - a_4)p_1^{-1} p_2 ^ {-1} \pmod {p_3}
k = ( a 3 − a 4 ) p 1 − 1 p 2 − 1 ( m o d p 3 )
所以:
a n s ≡ k p 1 p 2 + a 4 ( m o d p 1 p 2 p 3 ) ans \equiv kp_1 p_2 + a_4 \pmod {p_1p_2p_3}
a n s ≡ k p 1 p 2 + a 4 ( m o d p 1 p 2 p 3 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 #include <bits/stdc++.h> using namespace std;#define int __int128 const int N = 4e5 + 10 ;const int g = 3 ;int read () { int x = 0 , f = 1 ; char ch = getchar (); while (ch < '0' || ch > '9' ){if (ch == '-' )f = -1 ; ch = getchar ();} while (ch >= '0' && ch <= '9' ){x = x*10 + ch-'0' ; ch = getchar ();} return x * f; } void write (int x) { char ch[100 ], len = 0 ; if (x == 0 )ch[++len] = '0' ; while (x)ch[++len] = x%10 + '0' , x /= 10 ; while (len)putchar (ch[len--]); printf (" " ); } int p[3 ] = {469762049 , 998244353 , 1004535809 };int qpow (int a, int b, int i) { int t = 1 ; while (b != 0 ) { if (b & 1 ) t = t * a % p[i]; a = a * a % p[i]; b >>= 1 ; } return t % p[i]; } int inv (int x, int i) { return qpow (x, p[i] - 2 , i); } int gi[3 ];void init () { for (int i = 0 ; i < 3 ; i++) gi[i] = inv (g, i); } int F[N], G[N], H[N];int recover[N];void NTT (int *a, int len, int type, int f) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi[f], (p[f] - 1 ) / (k << 1 ), f); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % p[f]; int z = w * a[i + j + k] % p[f]; a[i + j] = (y + z) % p[f]; a[i + j + k] = (y - z + p[f]) % p[f]; w = w * x % p[f]; } } } if (type == -1 ) { int iv = inv (len, f); for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % p[f]; } } int A[N], B[N], C[3 ][N];void CRT (int len) { int M = p[0 ] * p[1 ]; for (int i = 0 ; i <= len; i++) { H[i] = (p[1 ] * C[0 ][i] % M * inv (p[1 ], 0 ) % M + p[0 ] * C[1 ][i] % M * inv (p[0 ], 1 ) % M) % M; } } int n, m, mod;void merge (int len) { for (int i = 0 ; i <= len; i++) { int k = ((C[2 ][i] - H[i]) % p[2 ] + p[2 ]) % p[2 ] * inv (p[0 ] * p[1 ], 2 ) % p[2 ]; H[i] = ((k * p[0 ] * p[1 ] % mod + H[i] % mod) % mod + mod) % mod; } } void prework () { memcpy (A, F, sizeof (F)); memcpy (B, G, sizeof (G)); } void update (int x, int len) { for (int i = 0 ; i < len; i++) C[x][i] = A[i] * B[i] % p[x]; } signed main () { init (); n = read (), m = read (), mod = read (); for (int i = 0 ; i <= n; i++) F[i] = read (); for (int i = 0 ; i <= m; i++) G[i] = read (); int len = 1 , cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); for (int i = 0 ; i < 3 ; i++) { prework (); NTT (A, len, 1 , i), NTT (B, len, 1 , i); update (i, len); NTT (C[i], len, -1 , i); } CRT (n + m); merge (n + m); for (int i = 0 ; i <= (n + m); i++) write (H[i]); return 0 ; }
多项式乘法逆
定义多项式F − 1 F ^ {-1} F − 1 为多项式F F F 的乘法逆元,满足
F ∗ F − 1 ≡ 1 ( m o d x n ) F \ast F ^ {-1} \equiv 1 \pmod{x^n}
F ∗ F − 1 ≡ 1 ( m o d x n )
假设我们已经得知F ∗ G ′ ≡ 1 ( m o d x n 2 ) F \ast G' \equiv 1 \pmod {x ^ {\frac{n}{2}}} F ∗ G ′ ≡ 1 ( m o d x 2 n ) , 来求F ∗ G ≡ 1 ( m o d x n ) F \ast G \equiv 1 \pmod {x ^ n} F ∗ G ≡ 1 ( m o d x n )
∵ F ∗ G ′ ≡ 1 m o d x n 2 , F ∗ G ≡ 1 ( m o d x n ) ∴ F ∗ G ≡ 1 ( m o d x n 2 ) ∴ G ′ − G ≡ 0 ( m o d x n 2 ) ∴ ( G ′ − G ) 2 ≡ 0 ( m o d x n ) G ′ 2 − 2 G G ′ + G 2 ≡ 0 ( m o d x n ) \because F \ast G' \equiv 1 \mod {x ^ {\frac{n}{2}}} , F \ast G \equiv 1 \pmod {x ^ n} \\
\therefore F \ast G \equiv 1 \pmod {x ^ {\frac{n}{2}}} \\
\therefore G' - G \equiv 0 \pmod {x ^ {\frac{n}{2}}} \\
\therefore (G' - G) ^ 2 \equiv 0 \pmod {x ^ n} \\
G'^2 - 2 G G' + G^2 \equiv 0 \pmod {x ^ n} \\
∵ F ∗ G ′ ≡ 1 m o d x 2 n , F ∗ G ≡ 1 ( m o d x n ) ∴ F ∗ G ≡ 1 ( m o d x 2 n ) ∴ G ′ − G ≡ 0 ( m o d x 2 n ) ∴ ( G ′ − G ) 2 ≡ 0 ( m o d x n ) G ′ 2 − 2 G G ′ + G 2 ≡ 0 ( m o d x n )
接下来两边同时∗ F \ast F ∗ F ,
F G ′ 2 − 2 G ′ + G ≡ 0 ( m o d x n ) ∴ G ≡ 2 G ′ − F G ′ 2 ( m o d x n ) F G'^2 - 2 G' + G \equiv 0 \pmod {x ^ n} \\
\therefore G \equiv 2 G' - FG'^2 \pmod {x ^ n}
F G ′ 2 − 2 G ′ + G ≡ 0 ( m o d x n ) ∴ G ≡ 2 G ′ − F G ′ 2 ( m o d x n )
然后直接递归即可, 使用NTT, 时间复杂度O ( n log n ) O(n \log n) O ( n log n ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 #include <bits/stdc++.h> using namespace std;#define int long long const int N = 4e5 + 10 ;const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 ); } int F[N], G[N];int recover[N];void NTT (int *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi, (mod - 1 ) / (k << 1 )); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = (y - z + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = inv (len); for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } int c[N];void mul (int n, int *a, int *b) { if (n == 1 ) { b[0 ] = inv (a[0 ]); return ; } mul ((n + 1 ) >> 1 , a, b); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); for (int i = 0 ; i < n; i++) c[i] = a[i]; for (int i = n; i < len; i++) c[i] = 0 ; NTT (c, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) b[i] = (2 - b[i] * c[i] % mod + mod) % mod * b[i] % mod; NTT (b, len, -1 ); for (int i = n; i < len; i++)b[i] = 0 ; } signed main () { int n; scanf ("%lld" , &n); for (int i = 0 ; i < n; i++) scanf ("%lld" , &F[i]); mul (n, F, G); for (int i = 0 ; i < n; i++) printf ("%lld " , (G[i] % mod + mod) % mod); return 0 ; }
多项式对数函数(多项式求ln)
定义多项式对数函数为
G = ln ( F ) ( m o d x n ) G = \ln (F) \pmod {x ^ n}
G = ln ( F ) ( m o d x n )
假设我们有多项式F ( x ) F(x) F ( x ) 和G ( x ) G(x) G ( x ) , 记G = ln F ( m o d x n ) G = \ln F \pmod {x ^ n} G = ln F ( m o d x n ) ,
G ≡ ln F ( m o d x n ) G ′ ≡ ( ln F ) ’ ( m o d x n ) G ′ ≡ ( ln ′ F ) ∗ F ′ ( m o d x n ) G ′ ≡ F ′ F ( m o d x n ) G \equiv \ln F \pmod {x ^ n} \\
G'\equiv (\ln F)’ \pmod {x ^ n} \\
G' \equiv (\ln' F )\ast F ' \pmod {x ^n} \\
G' \equiv \frac{F'}{F} \pmod {x^n}
G ≡ ln F ( m o d x n ) G ′ ≡ ( ln F ) ’ ( m o d x n ) G ′ ≡ ( ln ′ F ) ∗ F ′ ( m o d x n ) G ′ ≡ F F ′ ( m o d x n )
多项式求逆,再积回去就好啦。
需要用到求导:x a ′ = a x a − 1 x ^ {a'} = ax ^ {a - 1} x a ′ = a x a − 1 , 积分:∫ x a d x = 1 a + 1 x a + 1 \int x^a \mathrm{d}x = \frac{1}{a + 1}x ^ {a + 1} ∫ x a d x = a + 1 1 x a + 1 。需要保证 F 0 = 1 F_0 = 1 F 0 = 1 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 #include <bits/stdc++.h> using namespace std;#define int long long const int N = 4e5 + 10 ;const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;int recover[N];int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 ); } void NTT (int *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi, (mod - 1 ) / (k << 1 )); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = (y - z + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = inv (len); for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } void inverse (int *a, int *b, int n) { if (n == 1 ) { b[0 ] = inv (a[0 ]); return ; } inverse (a, b, (n + 1 ) >> 1 ); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); int c[N]; memset (c, 0 , sizeof (c)); for (int i = 0 ; i < n; i++) c[i] = a[i]; for (int i = n; i < len; i++) c[i] = 0 ; NTT (c, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) b[i] = (2 - b[i] * c[i] % mod + mod) % mod * b[i] % mod; NTT (b, len, -1 ); for (int i = n; i < len; i++)b[i] = 0 ; } void mul (int *a, int *b, int *c, int n, int m) { int len = 1 , cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); NTT (a, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) c[i] = a[i] * b[i] % mod; NTT (c, len, -1 ); } void add (int *a, int *b, int *c, int n, int m, int k) { for (int i = 0 ; i <= max (n, m); i++) c[i] = (a[i] + k * b[i] + mod) % mod; } void diff (int *a, int *b, int n) { for (int i = 1 ; i < n; i++) b[i - 1 ] = i * a[i] % mod; b[n - 1 ] = 0 ; } void integ (int *a, int *b, int n) { for (int i = 1 ; i < n; i++) b[i] = a[i - 1 ] * inv (i) % mod; b[0 ] = 0 ; } int F[N], G[N], H[N];void polyln (int *a, int *b, int n) { int f[N], h[N]; memset (f, 0 , sizeof (f)); memset (h, 0 , sizeof (h)); diff (a, f, n); inverse (a, h, n); mul (f, h, H, n, n); integ (H, b, n); } int n;signed main () { scanf ("%lld" , &n); for (int i = 0 ; i < n; i++) scanf ("%lld" , &F[i]); polyln (F, G, n); for (int i = 0 ; i < n; i++) printf ("%lld " , G[i]); return 0 ; }
多项式指数函数(多项式exp)
定义多项式指数函数为
G ( x ) = e F ( x ) ( m o d x n ) G(x) = e ^ {F(x)} \pmod {x ^ n}
G ( x ) = e F ( x ) ( m o d x n )
牛顿迭代
牛顿迭代 用于求函数零点,通过不断地切线逼近所求值,但最终也只是近似值,迭代的次数越多,精确度越高,误差越小。
假如我们要对一个非常大的数a a a 开方,手算,利用牛顿法来解决这个问题,其实本质上是求得f ( x ) = x 2 − a f(x) = x ^2 - a f ( x ) = x 2 − a 精确到整数得零点,假设我们已经求得了一个近似值x 0 x_0 x 0 ,那么我们只需要过( x 0 , f ( x 0 ) ) (x_0, f(x_0)) ( x 0 , f ( x 0 ) ) 这个点, 作这个函数图像的切线,取切线与x x x 轴的交点作为新的x 0 x_0 x 0 。
假设我们要求一个函数f ( x ) f(x) f ( x ) 的零点, 初始近似值是x 0 x_0 x 0 ,则切线方程为
y = f ′ ( x 0 ) ( x − x 0 ) + f ( x 0 ) y = f'(x_0)(x - x_0) + f(x_0)
y = f ′ ( x 0 ) ( x − x 0 ) + f ( x 0 )
令y = 0 y = 0 y = 0 ,得到x = x 0 − f ( x 0 ) f ′ ( x 0 ) x = x_0 - \frac{f(x_0)}{f'(x_0)} x = x 0 − f ′ ( x 0 ) f ( x 0 ) 。
假设我们现在要求F ( G ( x ) ) ≡ 0 F(G(x)) \equiv 0 F ( G ( x ) ) ≡ 0 ,然后利用上面的式子每一次令
G ( x ) = G 0 ( x ) − F ( G 0 ( x ) ) F ′ ( G 0 ( x ) ) G(x) = G_0(x) - \frac{F(G_0(x))}{F'(G_0(x))}
G ( x ) = G 0 ( x ) − F ′ ( G 0 ( x ) ) F ( G 0 ( x ) )
然后就可以很快的逼近真实值。
接下来推一下多项式exp
B ( x ) ≡ e A ( x ) ( m o d x n ) ln B ( x ) − A ( x ) ≡ 0 ( m o d x n ) B(x) \equiv e ^ {A(x)} \pmod {x ^ n} \\
\ln B(x) - A(x) \equiv 0 \pmod {x^ n}
B ( x ) ≡ e A ( x ) ( m o d x n ) ln B ( x ) − A ( x ) ≡ 0 ( m o d x n )
现在问题变为了使得F ( G ( x ) ) = ln G ( x ) − A ( x ) ≡ 0 F(G(x)) = \ln G(x) - A(x) \equiv 0 F ( G ( x ) ) = ln G ( x ) − A ( x ) ≡ 0 。
然后求导,
F ′ ( G 0 ( x ) ) = 1 G 0 ( x ) F'(G_0(x)) = \frac{1}{G_0(x)}
F ′ ( G 0 ( x ) ) = G 0 ( x ) 1
然后接着带入上面牛顿迭代的式子,
G ( x ) = G 0 ( x ) ( 1 − ln G 0 ( x ) + A ( x ) ) G(x) = {G_0(x)(1 - \ln G_0(x) + A(x))}
G ( x ) = G 0 ( x ) ( 1 − ln G 0 ( x ) + A ( x ) )
每次迭代,使用多项式求ln \ln ln ,然后再做一遍多项式乘法,然后就可以得到答案,时间复杂度O ( n log n ) O(n \log n) O ( n log n ) 。
需要保证 F 0 = 0 F_0 = 0 F 0 = 0 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 #include <bits/stdc++.h> using namespace std;#define int long long const int N = 4e5 + 10 ;const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;int recover[N];int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 ); } void NTT (int *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi, (mod - 1 ) / (k << 1 )); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = (y - z + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = inv (len); for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } void inverse (int *a, int *b, int n) { if (n == 1 ) { b[0 ] = inv (a[0 ]); return ; } inverse (a, b, (n + 1 ) >> 1 ); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); int c[N]; memset (c, 0 , sizeof (c)); for (int i = 0 ; i < n; i++) c[i] = a[i]; for (int i = n; i < len; i++) c[i] = 0 ; NTT (c, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) b[i] = (2 - b[i] * c[i] % mod + mod) % mod * b[i] % mod; NTT (b, len, -1 ); for (int i = n; i < len; i++)b[i] = 0 ; } void mul (int *a, int *b, int *c, int n, int m) { int len = 1 , cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); NTT (a, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) c[i] = a[i] * b[i] % mod; NTT (c, len, -1 ); } void add (int *a, int *b, int *c, int n, int m, int k) { for (int i = 0 ; i <= max (n, m); i++) c[i] = (a[i] + k * b[i] + mod) % mod; } void diff (int *a, int *b, int n) { for (int i = 1 ; i < n; i++) b[i - 1 ] = i * a[i] % mod; b[n - 1 ] = 0 ; } void integ (int *a, int *b, int n) { for (int i = 1 ; i < n; i++) b[i] = a[i - 1 ] * inv (i) % mod; b[0 ] = 0 ; } int F[N], G[N], H[N];void polyln (int *a, int *b, int n) { int f[N], h[N]; memset (f, 0 , sizeof (f)); memset (h, 0 , sizeof (h)); diff (a, f, n); inverse (a, h, n); mul (f, h, H, n, n); integ (H, b, n); } void polyexp (int *a, int *b, int n) { if (n == 1 ) { b[0 ] = 1 ; return ; } polyexp (a, b, (n + 1 ) >> 1 ); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); int c[N]; memset (c, 0 , sizeof (c)); c[0 ] = 1 ; int f[N]; memset (f, 0 , sizeof (f)); polyln (b, f, n); add (c, a, c, n, n, 1 ); add (c, f, c, n, n, -1 ); mul (c, b, c, n, n); for (int i = 0 ; i < n; i++) b[i] = c[i]; for (int i = n; i < len; i++) b[i] = 0 ; } int n;signed main () { scanf ("%lld" , &n); for (int i = 0 ; i < n; i++) scanf ("%lld" , &F[i]); polyexp (F, G, n); for (int i = 0 ; i < n; i++) printf ("%lld " , G[i]); return 0 ; }
多项式开根
多项式开根用来解决
G 2 ( x ) ≡ F ( x ) ( m o d x n ) G^2(x) \equiv F(x) \pmod {x^n}
G 2 ( x ) ≡ F ( x ) ( m o d x n )
假设我们有G ′ 2 ( x ) ≡ F ( x ) ( m o d x n 2 ) , H ( G ( x ) ) = G 2 ( x ) − F G'^2(x) \equiv F(x) \pmod {x ^ {\frac{n}{2}}}, H(G(x)) = G^2(x) - F G ′ 2 ( x ) ≡ F ( x ) ( m o d x 2 n ) , H ( G ( x ) ) = G 2 ( x ) − F ,求G 2 ( x ) ≡ F ( x ) ( m o d x n ) G^2(x) \equiv F(x) \pmod {x ^ n} G 2 ( x ) ≡ F ( x ) ( m o d x n ) ,
G ′ 2 ( x ) ≡ F ( x ) m o d x n 2 , G 2 ( x ) ≡ F ( x ) ( m o d x n 2 ) G 2 ( x ) − F ≡ 0 ( m o d x n 2 ) H ( G ) ≡ 0 ( m o d x n 2 ) G ≡ G ′ − H ( G ′ ) H ′ ( G ′ ) ( m o d x n ) G ≡ G ′ 2 + F 2 G ′ ( m o d x n ) G'^2 (x) \equiv F(x) \mod x ^ {\frac{n}{2}} , G^2(x) \equiv F(x) \pmod {x ^ {\frac{n}{2}}} \\
G^2(x) - F \equiv 0 \pmod {x ^ {\frac{n}{2}}} \\
H(G) \equiv 0 \pmod {x ^ {\frac{n}{2}}} \\
G \equiv G' - \frac{H(G')}{H'(G')} \pmod {x ^ n} \\
G \equiv \frac{G'^2 + F} {2G'} \pmod {x ^ n}
G ′ 2 ( x ) ≡ F ( x ) m o d x 2 n , G 2 ( x ) ≡ F ( x ) ( m o d x 2 n ) G 2 ( x ) − F ≡ 0 ( m o d x 2 n ) H ( G ) ≡ 0 ( m o d x 2 n ) G ≡ G ′ − H ′ ( G ′ ) H ( G ′ ) ( m o d x n ) G ≡ 2 G ′ G ′ 2 + F ( m o d x n )
需要保证F 0 = 1 F_0 = 1 F 0 = 1 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 #include <bits/stdc++.h> using namespace std;#define int long long const int N = 4e5 + 10 ;const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;int recover[N];int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 ); } int inv2 = inv (2 );void NTT (int *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi, (mod - 1 ) / (k << 1 )); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = (y - z + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = inv (len); for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } void inverse (int *a, int *b, int n) { if (n == 1 ) { b[0 ] = inv (a[0 ]); return ; } inverse (a, b, (n + 1 ) >> 1 ); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); int c[N]; memset (c, 0 , sizeof (c)); for (int i = 0 ; i < n; i++) c[i] = a[i]; for (int i = n; i < len; i++) c[i] = 0 ; NTT (c, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) b[i] = (2 - b[i] * c[i] % mod + mod) % mod * b[i] % mod; NTT (b, len, -1 ); for (int i = n; i < len; i++)b[i] = 0 ; } void mul (int *a, int *b, int *c, int n, int m) { int len = 1 , cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); NTT (a, len, 1 ), NTT (b, len, 1 ); for (int i = 0 ; i < len; i++) c[i] = a[i] * b[i] % mod; NTT (c, len, -1 ); } void add (int *a, int *b, int *c, int n, int m, int k) { for (int i = 0 ; i <= max (n, m); i++) c[i] = (a[i] + k * b[i] + mod) % mod; } int F[N], G[N], H[N];void polysqrt (int *a, int *b, int n) { if (n == 1 ) { b[0 ] = 1 ; return ; } polysqrt (a, b, (n + 1 ) >> 1 ); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); int c[N]; memset (c, 0 , sizeof (c)); int f[N]; memset (f, 0 , sizeof (f)); inverse (b, f, n); for (int i = 0 ; i < n; i++) c[i] = a[i]; NTT (f, len, 1 ), NTT (b, len, 1 ), NTT (c, len, 1 ); for (int i = 0 ; i < len; i++) b[i] = (b[i] + c[i] * f[i] % mod) % mod * inv2 % mod; NTT (b, len, -1 ); for (int i = n; i < len; i++) b[i] = 0 ; } int n;signed main () { scanf ("%lld" , &n); for (int i = 0 ; i < n; i++) scanf ("%lld" , &F[i]); polysqrt (F, G, n); for (int i = 0 ; i < n; i++) printf ("%lld " , G[i]); return 0 ; }
多项式幂函数
多项式幂函数 是用来解决
G ( x ) ≡ ( F ( x ) ) k m o d x n G(x) \equiv (F(x)) ^ k \mod x ^ n
G ( x ) ≡ ( F ( x ) ) k m o d x n
先求一遍ln \ln ln 然后乘以k k k 再使用exp \exp exp ,就好啦。
需保证 F 0 = 1 F_0 = 1 F 0 = 1 。
多项式的一些普通情况
多项式求ln
不保证F 0 = 1 F_0 = 1 F 0 = 1 。不存在,有定理:
在模意义下当且仅当F 0 = 1 F_0 = 1 F 0 = 1 , F ( x ) F(x) F ( x ) 有对数多项式问题。
多项式求exp
不保证F 0 = 0 F_0 = 0 F 0 = 0 。同多项式求ln \ln ln 。
多项式开根
不保证F 0 = 1 F_0 = 1 F 0 = 1 ,但保证F 0 F_0 F 0 是 m o d 998244353 \bmod 998244353 m o d 9 9 8 2 4 4 3 5 3 下的二次剩余。
边界求一遍二次剩余即可。
多项式幂函数
不保证F 0 = 1 F_0 = 1 F 0 = 1 。可以先找到系数不为0 0 0 的一项,然后让式子除以这一项最后再乘回来就好了
F ( x ) k = ( F ( x ) x t ) k x t k F(x)^k = \bigg( \frac{F(x)}{x ^ t} \bigg) ^ k x ^ {tk}
F ( x ) k = ( x t F ( x ) ) k x t k
分治FFT/NTT
给定序列g g g 和f f f , 其中
f i = ∑ j = 1 i f i − j g j f_i= \sum_{j = 1} ^ i f _{i - j} g _ j
f i = j = 1 ∑ i f i − j g j
求f f f , 这里给出一个多项式求逆的方法,(找时间再补分治FFT / NTT)
设F ( x ) = ∑ i = 0 ∞ f i x i , G ( x ) = ∑ i = 0 ∞ g i x i F(x) = \sum_{i = 0} ^ {\infty} f_i x ^ i , G(x) = \sum_{i = 0} ^ {\infty}g_i x ^ i F ( x ) = ∑ i = 0 ∞ f i x i , G ( x ) = ∑ i = 0 ∞ g i x i ,且g 0 = 0 g_0 = 0 g 0 = 0 ,
所以有
F ( x ) G ( x ) = ∑ i = 0 ∞ ∑ j + k = i f j g k = F ( x ) − f 0 x 0 F ( x ) G ( x ) ≡ F ( x ) − f 0 ( m o d x n ) F ( x ) ≡ ( 1 − G ( x ) ) − 1 ( m o d x n ) F(x) G(x) = \sum_{i = 0} ^ {\infty} \sum_{j + k = i}f_jg_k = F(x) - f_0 x ^ 0 \\
F(x)G(x) \equiv F(x) - f_0 \pmod {x ^ n} \\
F(x) \equiv (1 - G(x)) ^ {-1} \pmod {x ^ n}
F ( x ) G ( x ) = i = 0 ∑ ∞ j + k = i ∑ f j g k = F ( x ) − f 0 x 0 F ( x ) G ( x ) ≡ F ( x ) − f 0 ( m o d x n ) F ( x ) ≡ ( 1 − G ( x ) ) − 1 ( m o d x n )
下降幂多项式乘法
假设我们已知n n n 次多项式f ( x ) f(x) f ( x ) 在[ 0 , n ] [0, n] [ 0 , n ] 的点值, 求它的下降幂表示,
设f ( x ) = ∑ i = 0 n b i x i ‾ = ∑ i = 0 n b i x ! ( x − i ) ! f(x) = \sum_{i = 0} ^ n b_i x^{\underline{i}} = \sum_{i = 0} ^ n b_i\frac{x!}{(x -i)!} f ( x ) = ∑ i = 0 n b i x i = ∑ i = 0 n b i ( x − i ) ! x ! ,则有
f ( x ) x ! = ∑ i = 0 n b i 1 ( x − i ) ! = b ∗ e x \frac{f(x)}{x!} = \sum_{i = 0} ^ n b_i \frac{1}{(x - i) !} = b \ast e^x
x ! f ( x ) = i = 0 ∑ n b i ( x − i ) ! 1 = b ∗ e x
先转化为点值最后卷上e x e^x e x 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 #include <bits/stdc++.h> using namespace std;#define int long long const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;const int N = 8e5 + 10 ;int recover[N];int n, m;int A[N], B[N], F[N], G[N], H[N];int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 ); } void NTT (int *a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); for (int k = 1 ; k < len; k <<= 1 ) { int x = qpow (type == 1 ? g : gi, (mod - 1 ) / (k << 1 )); for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = ((y - z) % mod + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = inv (len); for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } int fac[N], ifac[N];signed main () { scanf ("%lld%lld" , &n, &m); for (int i = 0 ; i <= n; i++) scanf ("%lld" , &F[i]); for (int i = 0 ; i <= m; i++) scanf ("%lld" , &G[i]); int len = 1 , cnt = 0 , Len = max (n, m) << 1 ; while (len <= (Len << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); fac[0 ] = ifac[0 ] = 1 ; for (int i = 1 ; i <= Len; i++) fac[i] = fac[i - 1 ] * i % mod; for (int i = 1 ; i <= Len; i++) ifac[i] = inv (fac[i]); for (int i = 0 ; i <= Len; i++) { if (i & 1 )B[i] = ((mod - ifac[i]) % mod + mod) % mod; else B[i] = ifac[i]; A[i] = ifac[i]; } NTT (A, len, 1 ); NTT (B, len, 1 ); NTT (F, len, 1 ); NTT (G, len, 1 ); for (int i = 0 ; i < len; i++) { F[i] = A[i] * F[i] % mod; G[i] = A[i] * G[i] % mod; } NTT (F, len, -1 ); NTT (G, len, -1 ); for (int i = 0 ; i <= Len; i++) H[i] = F[i] % mod * G[i] % mod * fac[i] % mod; NTT (H, len, 1 ); for (int i = 0 ; i < len; i++) H[i] = H[i] * B[i] % mod; NTT (H, len, -1 ); for (int i = 0 ; i <= n + m; i++) printf ("%lld " , H[i]); return 0 ; }
多项式除法
给定一个n n n 次多项式F ( x ) F(x) F ( x ) 和一个m m m 次多项式G ( x ) G(x) G ( x ) , 求多项式A ( x ) , B ( x ) A(x),B(x) A ( x ) , B ( x ) ,满足:
A ( x ) A(x) A ( x ) 次数为n − m n - m n − m , B ( x ) B(x) B ( x ) 次数小于m m m
F ( x ) = A ( x ) ∗ G ( x ) + B ( x ) F(x) = A(x) \ast G(x) + B(x) F ( x ) = A ( x ) ∗ G ( x ) + B ( x )
所有运算在模998244353 998244353 9 9 8 2 4 4 3 5 3 下进行。
定义一种让多项式反转的操作为A ′ ( x ) = x n A ( 1 x ) A'(x) = x^n A(\frac{1}{x}) A ′ ( x ) = x n A ( x 1 ) ,然后化简式子
F ( x ) = A ( x ) ∗ G ( x ) + B ( x ) x n F ( 1 x ) = x n − m A ( 1 x ) ∗ x m G ( 1 x ) + x n − m + 1 ∗ x m − 1 B ( 1 x ) F ′ ( x ) = A ′ ( x ) ∗ G ′ ( x ) + x n − m + 1 ∗ B ′ ( x ) F ′ ( x ) ≡ A ′ ( x ) ∗ G ′ ( x ) ( m o d x n − m + 1 ) A ′ ( x ) ≡ F ′ ( x ) ∗ G ′ − 1 ( x ) ( m o d x n − m + 1 ) F(x) = A(x) \ast G(x) + B(x) \\
x^n F(\frac{1}{x}) = x^{n - m} A(\frac{1}{x}) \ast x^m G(\frac{1}{x}) + x^{n - m + 1}\ast x ^{m - 1} B(\frac{1}{x}) \\
F'(x) = A'(x) \ast G'(x) + x^{n - m + 1} \ast B'(x) \\
F'(x) \equiv A'(x) \ast G'(x) \pmod {x^{n - m + 1}} \\
A'(x) \equiv F'(x) \ast G'^{-1}(x) \pmod {x^{n - m + 1}}
F ( x ) = A ( x ) ∗ G ( x ) + B ( x ) x n F ( x 1 ) = x n − m A ( x 1 ) ∗ x m G ( x 1 ) + x n − m + 1 ∗ x m − 1 B ( x 1 ) F ′ ( x ) = A ′ ( x ) ∗ G ′ ( x ) + x n − m + 1 ∗ B ′ ( x ) F ′ ( x ) ≡ A ′ ( x ) ∗ G ′ ( x ) ( m o d x n − m + 1 ) A ′ ( x ) ≡ F ′ ( x ) ∗ G ′ − 1 ( x ) ( m o d x n − m + 1 )
先进行多项式求逆,然后再推B ( x ) = F ( x ) − A ( x ) ∗ G ( x ) B(x) = F(x) - A(x) \ast G(x) B ( x ) = F ( x ) − A ( x ) ∗ G ( x ) 。
多项式多点求值
咕咕咕
多项式复合函数
有F ( x ) , G ( x ) F(x), G(x) F ( x ) , G ( x ) ,求
H ( x ) ≡ F ( G ( x ) ) ( m o d x n + 1 ) H(x) \equiv F(G(x)) \pmod {x^{n + 1}}
H ( x ) ≡ F ( G ( x ) ) ( m o d x n + 1 )
即:
H ( x ) ≡ ∑ i = 0 n [ x i ] F ( x ) × G ( x ) i ( m o d x n + 1 ) H(x) \equiv \sum_{i = 0} ^n[x^i] F(x) \times G(x)^i \pmod {x^{n + 1}}
H ( x ) ≡ i = 0 ∑ n [ x i ] F ( x ) × G ( x ) i ( m o d x n + 1 )
对998244353 998244353 9 9 8 2 4 4 3 5 3 取模。
设m = n m = \sqrt n m = n ,则
∑ i = 0 n [ x i ] F ( x ) G ( x ) i = ∑ i = 0 m − 1 ∑ j = 0 m − 1 [ x i m + j ] F ( x ) G ( x ) i m + j = ∑ i = 0 m − 1 G ( x ) i m ∑ j = 0 m − 1 [ x i m + j ] F ( x ) G ( x ) j \sum_{i = 0} ^ n [x^i] F(x)G(x) ^ i = \sum_{i = 0} ^ {m - 1} \sum_{j = 0} ^ {m - 1}[x^{im + j}]F(x)G(x)^{im + j} = \sum_{i = 0}^{m - 1}G(x)^{im}\sum_{j = 0} ^ {m - 1}[x^{im+j}]F(x)G(x)^j
i = 0 ∑ n [ x i ] F ( x ) G ( x ) i = i = 0 ∑ m − 1 j = 0 ∑ m − 1 [ x i m + j ] F ( x ) G ( x ) i m + j = i = 0 ∑ m − 1 G ( x ) i m j = 0 ∑ m − 1 [ x i m + j ] F ( x ) G ( x ) j
然后预处理G ( x ) i m G(x)^{im} G ( x ) i m 和G ( x ) j G(x)^j G ( x ) j ,其它的直接暴力计算, 时间复杂度O ( n 2 + n n log n ) O(n^2 + n \sqrt n \log n) O ( n 2 + n n log n ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 #include <bits/stdc++.h> using namespace std;namespace Poly{ #define int long long #define vec vector <int> const int mod = 998244353 ; const int g = 3 ; const int gi = 332748118 ; const int N = 8e4 + 10 ; int save[3 ][32 ]; int recover[N]; int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 );} void prework () { for (int i = 1 , k = 1 ; i <= 20 ; i++, k <<= 1 ) { save[0 ][i] = qpow (g, (mod - 1 ) / (k << 1 )); save[1 ][i] = qpow (gi, (mod - 1 ) / (k << 1 )); save[2 ][i] = inv (k); } } void init (int n, int m, int &len) { len = 1 ; int cnt = 0 ; while (len <= (n + m))len <<= 1 , cnt ++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); } void NTT (vec &a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); int cnt = 1 ; for (int k = 1 ; k < len; k <<= 1 , cnt++) { int x = (type == 1 ) ? save[0 ][cnt] : save[1 ][cnt]; for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = ((y - z) % mod + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = save[2 ][cnt]; for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } struct poly { vector <int > v; int len; poly (){v.resize (N); len = 0 ;} void clear (int n) {v.clear (); v.resize (N); len = n;} void length (int n) {len = n;} void memset0 (int l, int r) {for (int i = l; i < r; i++)v[i] = 0 ;} void print (int n) {for (int i = 0 ; i < n; i++)printf ("%lld " , v[i]); printf ("\n" );} friend poly operator + (poly A, poly B) { A.length (max (A.len, B.len)); for (int i = 0 ; i <= A.len; i++) A.v[i] = (A.v[i] + B.v[i]) % mod; return A; } friend poly operator - (poly A, poly B) { A.length (max (A.len, B.len)); for (int i = 0 ; i <= A.len; i++) A.v[i] = ((A.v[i] - B.v[i]) % mod + mod) % mod; return A; } friend poly operator * (poly A, poly B) { int len; init (A.len, B.len, len); NTT (A.v, len, 1 ), NTT (B.v, len, 1 ); for (int i = 0 ; i < len; i++) A.v[i] = (A.v[i] * B.v[i]) % mod; NTT (A.v, len, -1 ); A.len += B.len; return A; } }; vec tmp; void inverse (poly &A, poly &B, int n) { if (n == 1 ){B.v[0 ] = inv (A.v[0 ]);return ;} inverse (A, B, (n + 1 ) >> 1 ); int len; init (n, n, len); tmp.clear (); tmp.resize (len); for (int i = 0 ; i < n; i++) tmp[i] = A.v[i]; NTT (tmp, len, 1 ), NTT (B.v, len, 1 ); for (int i = 0 ; i < len; i++) B.v[i] = (2 - B.v[i] * tmp[i] % mod + mod) % mod * B.v[i] % mod; NTT (B.v, len, -1 ); for (int i = n; i < len; i++)B.v[i] = 0 ; } void diff (poly &A, poly &B, int n) { for (int i = 1 ; i < n; i++) B.v[i - 1 ] = i * A.v[i] % mod; B.v[n - 1 ] = 0 ; B.length (n); } void integ (poly &A, poly &B, int n) { for (int i = 1 ; i < n; i++) B.v[i] = A.v[i - 1 ] * inv (i) % mod; B.v[0 ] = 0 ; B.length (n); } poly C, D, E, F, G, H, I; void Ln (poly &A, poly &B, int n) { E.clear (n); F.clear (n); diff (A, E, n); inverse (A, F, n); E = E * F; integ (E, B, n); B.length (n); } void Exp (poly &A, poly &B, int n) { if (n == 1 ){B.v[0 ] = 1 ; return ;} Exp (A, B, (n + 1 ) >> 1 ); int len; init (n, n, len); C.clear (n); D.clear (n); C.v[0 ] = 1 ; Ln (B, D, n); C = B * (C + A - D); for (int i = 0 ; i < n; i++)B.v[i] = C.v[i]; for (int i = n; i < len; i++)B.v[i] = 0 ; } const int inv2 = inv (2 ); void Sqrt (poly &A, poly &B, int n) { if (n == 1 ){B.v[0 ] = 1 ; return ;} Sqrt (A, B, (n + 1 ) >> 1 ); int len; init (n, n, len); G.clear (n); H.clear (n); inverse (B, H, n); for (int i = 0 ; i < n; i++)G.v[i] = A.v[i]; NTT (H.v, len, 1 ), NTT (B.v, len, 1 ), NTT (G.v, len, 1 ); for (int i = 0 ; i < len; i++) B.v[i] = (B.v[i] + G.v[i] * H.v[i] % mod) % mod * inv2 % mod; NTT (B.v, len, -1 ); for (int i = n; i < len; i++)B.v[i] = 0 ; } void Pow (poly &A, poly &B, int n, int k) { I.clear (n); Ln (A, I, n); for (int i = 0 ; i < n; i++)(I.v[i] *= k) %= mod; Exp (I, B, n); } #undef int } using namespace Poly;int n, m;poly Gpow[200 ], Gm[200 ]; int main () { prework (); cin >> n >> m; n = n + 1 ; m = m + 1 ; poly F, G, H; F.clear (n); G.clear (m); for (int i = 0 ; i < n; i++) cin >> F.v[i]; for (int i = 0 ; i < m; i++) cin >> G.v[i]; int l = sqrt (n) + 1 ; Gpow[0 ].v[0 ] = 1 ; Gm[0 ].v[0 ] = 1 ; Gpow[0 ].length (n); Gm[0 ].length (n); Gpow[1 ] = G; Gpow[1 ].length (n); for (int i = 2 ; i <= l; i++) { Gpow[i] = Gpow[i - 1 ] * G; Gpow[i].length (n); Gpow[i].memset0 (n, n << 1 ); } Gm[1 ] = Gpow[l]; Gm[1 ].length (n); for (int i = 2 ; i <= l; i++) { Gm[i] = Gm[i - 1 ] * Gpow[l]; Gm[i].length (n); Gm[i].memset0 (n, n << 1 ); } poly A; A.clear (n); H.clear (n); for (int i = 0 ; i < l; i++) { A.memset0 (0 , n << 1 ); A.length (n); for (int j = 0 ; j < l; j++) { for (int k = 0 ; k < n; k++) A.v[k] = (A.v[k] + F.v[i * l + j] * Gpow[j].v[k]) % mod; } A = A * Gm[i]; A.length (n); H = H + A; } for (int i = 0 ; i < n; i++) cout << H.v[i] << " " ; return 0 ; }
多项式复合逆
有F ( x ) F(x) F ( x ) ,求
G ( F ( x ) ) ≡ x ( m o d x n ) G(F(x)) \equiv x \pmod {x ^ n}
G ( F ( x ) ) ≡ x ( m o d x n )
对998244353 998244353 9 9 8 2 4 4 3 5 3 取模。
有拉格朗日反演公式:
[ x n ] G ( x ) = 1 n [ x n − 1 ] ( x F ( x ) ) n [x^n]G(x) = \frac{1}{n}[x^{n - 1}](\frac{x}{F(x)})^n
[ x n ] G ( x ) = n 1 [ x n − 1 ] ( F ( x ) x ) n
这个是求单项系数的, 需要求的是所有系数。
利用解决多项式复合函数的方法, 令m = n m = \sqrt n m = n , 则
G ( x ) = ∑ i = 1 n ( 1 i [ x i − 1 ] ( x F ( x ) ) i ) = ∑ i = 0 m − 1 ∑ j = 1 m ( 1 i m + j [ x i m + j − 1 ] ( x F ( x ) ) i m + j ) x i m + j = ∑ i = 0 m − 1 ∑ j = 1 m ( 1 i m + j [ x i m + j − 1 ] ( x F ( x ) ) i m ( x F ( x ) ) j ) x i m + j G(x) = \sum_{i = 1}^{n}\bigg ( \frac{1}{i}[x^{i - 1}] (\frac{x}{F(x)})^i \bigg) = \sum_{i = 0}^{m - 1}\sum_{j = 1} ^{m }\bigg( \frac{1}{im + j}[x^{im + j - 1}](\frac{x}{F(x)})^{im + j} \bigg)x^{im + j} = \sum_{i = 0}^{m - 1}\sum_{j = 1} ^ m \bigg( \frac{1}{im + j} [x^{im + j - 1}](\frac{x}{F(x)})^im (\frac{x}{F(x)})^j \bigg)x^{im + j}
G ( x ) = i = 1 ∑ n ( i 1 [ x i − 1 ] ( F ( x ) x ) i ) = i = 0 ∑ m − 1 j = 1 ∑ m ( i m + j 1 [ x i m + j − 1 ] ( F ( x ) x ) i m + j ) x i m + j = i = 0 ∑ m − 1 j = 1 ∑ m ( i m + j 1 [ x i m + j − 1 ] ( F ( x ) x ) i m ( F ( x ) x ) j ) x i m + j
然后就是和多项式复合函数的一样的处理方法,时间复杂度O ( n 2 + n log n ) O(n^2 + \sqrt n \log n) O ( n 2 + n log n ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 #include <bits/stdc++.h> using namespace std;#define int long long #define vec vector <int> const int N = 8e4 + 10 ;const int mod = 998244353 ;const int g = 3 ;const int gi = 332748118 ;struct poly { vector <int > v; poly (){v.resize (N);} }; int save[3 ][32 ];int recover[N];int qpow (int a, int b) { int t = 1 ; while (b != 0 ) { if (b & 1 )t = t * a % mod; a = a * a % mod; b >>= 1 ; } return t; } int inv (int x) { return qpow (x, mod - 2 ); } void prework () { for (int i = 1 , k = 1 ; i <= 20 ; i++, k <<= 1 ) { save[0 ][i] = qpow (g, (mod - 1 ) / (k << 1 )); save[1 ][i] = qpow (gi, (mod - 1 ) / (k << 1 )); save[2 ][i] = inv (k); } } void NTT (vec &a, int len, int type) { for (int i = 0 ; i < len; i++) if (i < recover[i])swap (a[i], a[recover[i]]); int cnt = 1 ; for (int k = 1 ; k < len; k <<= 1 , cnt++) { int x = (type == 1 ) ? save[0 ][cnt] : save[1 ][cnt]; for (int i = 0 ; i < len; i += (k << 1 )) { int w = 1 ; for (int j = 0 ; j < k; j++) { int y = a[i + j] % mod; int z = w * a[i + j + k] % mod; a[i + j] = (y + z) % mod; a[i + j + k] = ((y - z) % mod + mod) % mod; w = w * x % mod; } } } if (type == -1 ) { int iv = save[2 ][cnt]; for (int i = 0 ; i < len; i++) a[i] = a[i] * iv % mod; } } vec tmp; void inverse (poly &A, poly &B, int n) { if (n == 1 ){B.v[0 ] = inv (A.v[0 ]);return ;} inverse (A, B, (n + 1 ) >> 1 ); int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); tmp.clear (); tmp.resize (len); for (int i = 0 ; i < n; i++) tmp[i] = A.v[i]; NTT (tmp, len, 1 ), NTT (B.v, len, 1 ); for (int i = 0 ; i < len; i++) B.v[i] = (2 - B.v[i] * tmp[i] % mod + mod) % mod * B.v[i] % mod; NTT (B.v, len, -1 ); for (int i = n; i < len; i++)B.v[i] = 0 ; } int n;poly Finv[200 ], Fm[200 ]; signed main () { poly F; prework (); cin >> n; for (int i = 0 ; i < n; i++) cin >> F.v[i]; for (int i = 0 ; i < n; i++) F.v[i] = F.v[i + 1 ]; n = n - 1 ; int m = sqrt (n) + 1 ; poly A; inverse (F, A, n); Finv[0 ].v[0 ] = Fm[0 ].v[0 ] = 1 ; int len = 1 , cnt = 0 ; while (len <= (n << 1 ))len <<= 1 , cnt++; for (int i = 0 ; i < len; i++) recover[i] = (recover[i >> 1 ] >> 1 ) | ((i & 1 ) << (cnt - 1 )); Finv[1 ] = A; NTT (A.v, len, 1 ); for (int i = 2 ; i <= m; i++) { NTT (Finv[i - 1 ].v, len, 1 ); for (int j = 0 ; j < len; j++) Finv[i].v[j] = Finv[i - 1 ].v[j] * A.v[j] % mod; NTT (Finv[i].v, len, -1 ); NTT (Finv[i - 1 ].v, len, -1 ); for (int j = n; j < (n << 1 ); j++) Finv[i].v[j] = 0 ; } A = Finv[m]; NTT (A.v, len, 1 ); Fm[1 ] = Finv[m]; for (int i = 2 ; i <= m; i++) { NTT (Fm[i - 1 ].v, len, 1 ); for (int j = 0 ; j < len; j++) Fm[i].v[j] = Fm[i - 1 ].v[j] * A.v[j] % mod; NTT (Fm[i].v, len, -1 ); NTT (Fm[i - 1 ].v, len, -1 ); for (int j = n; j < (n << 1 ); j++) Fm[i].v[j] = 0 ; } poly G; bool res = false ; for (int i = 0 ; i <= m; i++) { for (int j = 1 ; j <= m; j++) { if (i * m + j - 1 > n) { res = true ; break ; } int sum = 0 ; for (int k = 0 ; k <= i * m + j - 1 ; k++) sum = (sum + Finv[j].v[k] * Fm[i].v[i * m + j - 1 - k] % mod) % mod; G.v[i * m + j] = sum * inv (i * m + j) % mod; } if (res)break ; } for (int i = 0 ; i <= n; i++) cout << G.v[i] << " " ; return 0 ; }