前言
在 OI 中,许多涉及到生成函数的计数题都需要使用一些多项式算法,所以掌握多项式算法是必要的。
常见导数
多项式加减法
加减法只要对应系数加减即可,这是线性的。
多项式乘法
乘法可以使用 FFT 完成。假设要计算 $H(x) = F(x) G(x)$,那么只需要把 $F(x), G(x)$ 进行 DFT,然后对应点值相乘后再 IDFT 回去就行了,时间复杂度 $O(n \log n)$。
多项式求导与积分
对于多项式 $F(x) = \sum_{i = 0}^{n} a_i x^i$,我们有:
可以直接线性计算。
泰勒展开
泰勒展开是多项式牛顿迭代的前置技能,它的思想就是用多项式来逼近一个函数。定义函数 $f(x)$ 在点 $x_0$ 的泰勒展开为:
其中 $f^{(i)}(x)$ 表示函数 $f(x)$ 的 $i$ 阶导数。 当然 $f(x)$ 不仅可以以数为参数,也可以以函数为参数。
多项式牛顿迭代
多项式牛顿迭代可以解决多项式求逆和多项式开根,它也是多项式指数函数的前置技能之一。
问题:给定 $G(x)$,构造 $F(x)$,满足 $G(F(x)) \equiv 0 \pmod {x^n}$。
假设我们已经构造出了 $F_0(x)$ 满足 $G(F_0(x)) \equiv 0 \pmod{x^{\lceil \frac{n}{2} \rceil}}$,现在我们要求出 $F(x)$。考虑 $G(F(x))$ 在 $F_0(x)$ 上的泰勒展开:
发现 $F(x)$ 和 $F_0(x)$ 的前 $\lceil \frac{n}{2} \rceil$ 项应该是一样的,所以我们就有:
所以就可以递归了,如果一次递归可以做到 $O(n \log n)$ 那么时间复杂度就是 $T(n) = T(\frac{n}{2}) + O(n \log n) = O(n \log n)$。
多项式求逆
问题:给定 $H(x)$,求 $F(x) H(x) \equiv 1 \pmod {x^n}$。
考虑令 $G(F(x)) = F^{-1}(x) - H(x)$,并使用牛顿迭代法。那么我们有:
对于 $n = 1$ 可以直接求逆元,总时间复杂度 $O(n \log n)$。
多项式开方
问题:给定 $H(x)$,求 $F(x)$ 满足 $F^2(x) \equiv H(x) \pmod{x^n}$。
考虑令 $G(F(x)) = F^{2}(x) - H(x)$,并使用牛顿迭代法。那么我们有:
对于 $n = 1$ 需要用到二次剩余,总复杂度 $O(n \log n)$。
多项式对数函数
已知 $F(x)$,我们要求 $\ln F(x)$。
考虑 $\ln$ 的意义。我们有 $(\ln x)’ = x^{-1}$,我们将 $x^{-1}$ 展开:
那么有 $(\ln x)’ = \sum_{i = 0}^{\infty} (1 - x)^i$,两边同时积分得到:
那么多项式的 $F(x)$ 的 $\ln$ 就是:
注意 $F(x)$ 的常数项必须为 $1$。怎么求 $\ln F(x) \pmod{x^n}$ 呢?考虑复合函数求导:
两边同时积分得到:
那么使用多项式求逆计算即可,时间复杂度 $O(n \log n)$。
多项式指数函数
问题:给定 $H(x)$,求 $F(x) = \exp(H(x)) \pmod{x^n}$。
考虑令 $G(F(x)) = \ln F(x) - H(x)$,并使用牛顿迭代法。那么我们有:
$n = 1$ 时 $H(x)$ 必须是 $0$,此时 $F(x) = 1$,总复杂度 $O(n \log n)$。
代码实现
1 | //「Luogu 5245」Polynomial Power |
2 |
|
3 | using namespace std; |
4 | |
5 | const int maxn = 1 << 18, g = 3, mod = 998244353; |
6 | int n, m, k, a[maxn + 3], b[maxn + 3]; |
7 | char s[maxn + 3]; |
8 | |
9 | namespace poly { |
10 | int lim, bit, rev[maxn + 3]; |
11 | int A[maxn + 3], B[maxn + 3], C[maxn + 3], D[maxn + 3]; |
12 | int E[maxn + 3], F[maxn + 3], G[maxn + 3], H[maxn + 3]; |
13 | |
14 | int qpow(int a, int b) { |
15 | if (b < 0) b += mod - 1; |
16 | int c = 1; |
17 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
18 | if (b & 1) c = 1ll * a * c % mod; |
19 | } |
20 | return c; |
21 | } |
22 | |
23 | int func(int x) { |
24 | return x < mod ? x : x - mod; |
25 | } |
26 | |
27 | void dft(int a[], int n, int type) { |
28 | for (int i = 0; i < n; i++) { |
29 | if (i < rev[i]) swap(a[i], a[rev[i]]); |
30 | } |
31 | for (int k = 1; k < n; k <<= 1) { |
32 | int x = qpow(g, (mod - 1) / (k << 1) * type); |
33 | for (int i = 0; i < n; i += k << 1) { |
34 | int y = 1; |
35 | for (int j = i; j < i + k; j++, y = 1ll * x * y % mod) { |
36 | int p = a[j], q = 1ll * a[j + k] * y % mod; |
37 | a[j] = func(p + q), a[j + k] = func(p - q + mod); |
38 | } |
39 | } |
40 | } |
41 | if (type == -1) { |
42 | int x = qpow(n, mod - 2); |
43 | for (int i = 0; i < n; i++) { |
44 | a[i] = 1ll * a[i] * x % mod; |
45 | } |
46 | } |
47 | } |
48 | |
49 | void mult(int a[], int b[], int c[], int n) { |
50 | // a = b = c is ok |
51 | for (lim = 1, bit = 0; lim <= n * 2; lim <<= 1) bit++; |
52 | for (int i = 1; i < lim; i++) { |
53 | rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1)); |
54 | } |
55 | copy(a, a + lim, A); |
56 | copy(b, b + lim, B); |
57 | fill(A + n + 1, A + lim, 0); |
58 | fill(B + n + 1, B + lim, 0); |
59 | dft(A, lim, 1), dft(B, lim, 1); |
60 | for (int i = 0; i < lim; i++) { |
61 | A[i] = 1ll * A[i] * B[i] % mod; |
62 | } |
63 | dft(A, lim, -1); |
64 | copy(A, A + n * 2 + 1, c); |
65 | } |
66 | |
67 | void inv(int a[], int b[], int n) { |
68 | // a != b |
69 | if (n == 0) { |
70 | b[0] = qpow(a[0], mod - 2); |
71 | return; |
72 | } |
73 | int m = n >> 1; |
74 | inv(a, b, m); |
75 | copy(a, a + n + 1, C); |
76 | copy(b, b + m + 1, D); |
77 | fill(D + m + 1, D + n + 1, 0); |
78 | mult(D, D, D, m); |
79 | mult(C, D, C, n); |
80 | for (int i = 0; i <= n; i++) { |
81 | b[i] = func(b[i] * 2 % mod - C[i] + mod); |
82 | } |
83 | } |
84 | |
85 | void sqrt(int a[], int b[], int n) { |
86 | // a != b |
87 | if (n == 0) { |
88 | assert(a[0] == 1); |
89 | b[0] = 1; |
90 | return; |
91 | } |
92 | int m = n >> 1; |
93 | sqrt(a, b, m); |
94 | copy(b, b + m + 1, E); |
95 | fill(E + m + 1, E + n + 1, 0); |
96 | fill(F, F + n + 1, 0); |
97 | mult(E, E, E, m); |
98 | for (int i = 0; i <= n; i++) { |
99 | E[i] = func(E[i] + a[i]); |
100 | } |
101 | inv(b, F, n); |
102 | mult(E, F, E, n); |
103 | for (int i = 0; i <= n; i++) { |
104 | b[i] = 1ll * (mod + 1) / 2 * E[i] % mod; |
105 | } |
106 | } |
107 | |
108 | void ln(int a[], int b[], int n) { |
109 | // a = b is ok |
110 | for (int i = 1; i <= n; i++) { |
111 | E[i - 1] = 1ll * a[i] * i % mod; |
112 | } |
113 | E[n] = 0; |
114 | fill(F, F + n + 1, 0); |
115 | inv(a, F, n); |
116 | mult(E, F, E, n); |
117 | b[0] = 0; |
118 | for (int i = 1; i <= n; i++) { |
119 | b[i] = 1ll * E[i - 1] * qpow(i, mod - 2) % mod; |
120 | } |
121 | } |
122 | |
123 | void exp(int a[], int b[], int n) { |
124 | // a != b |
125 | if (n == 0) { |
126 | assert(a[0] == 0); |
127 | b[0] = 1; |
128 | return; |
129 | } |
130 | int m = n >> 1; |
131 | exp(a, b, m); |
132 | copy(b, b + m + 1, G); |
133 | fill(G + m + 1, G + n + 1, 0); |
134 | copy(G, G + n + 1, H); |
135 | ln(H, H, n); |
136 | for (int i = 0; i <= n; i++) { |
137 | H[i] = func(a[i] - H[i] + mod); |
138 | } |
139 | H[0] = func(H[0] + 1); |
140 | mult(G, H, G, n); |
141 | copy(G, G + n + 1, b); |
142 | } |
143 | |
144 | void pow(int a[], int b[], int n, int k) { |
145 | // a != b |
146 | ln(a, a, n); |
147 | for (int i = 0; i <= n; i++) { |
148 | a[i] = 1ll * a[i] * k % mod; |
149 | } |
150 | exp(a, b, n); |
151 | } |
152 | } |
153 | |
154 | int main() { |
155 | scanf("%d %s", &n, s + 1); |
156 | n--, m = strlen(s + 1); |
157 | for (int i = 1; i <= m; i++) { |
158 | k = ((10ll * k) + (s[i] ^ '0')) % mod; |
159 | } |
160 | for (int i = 0; i <= n; i++) { |
161 | scanf("%d", &a[i]); |
162 | } |
163 | poly::pow(a, b, n, k); |
164 | for (int i = 0; i <= n; i++) { |
165 | printf("%d%c", b[i], " \n"[i == n]); |
166 | } |
167 | return 0; |
168 | } |