多项式全家桶

多项式运算从入门到入坟

目录

  • 多项式运算从入门到入坟
  • 1. 前言和前置知识
  • 2. 微积分基础知识
  • 2. 1 积分
  • 2. 2 导数
  • 2. 3 不定积分和微积分基本定理
  • 2. 4 基本函数求导公式
  • 2. 5 求导运算法则
  • 2. 6 常用积分公式
  • 2. 7 泰勒展开和泰勒级数
  • 3. 牛顿迭代
  • 4. 多项式求逆
  • 5. 多项式求导
  • 6. 多项式积分
  • 7. 多项式对数函数
  • 8. 多项式指数函数
  • 9. 多项式快速幂
  • 10. 多项式开方
  • 11. 多项式带余除法
  • 12. 多项式三角函数
  • 13. 多项式多点求值
  • 14. 多项式快速插值
  • 15. 拉格朗日反演
  • 16. 分治 NTT
  • 17. 完结撒花

1. 前言和前置知识

蛤?不会 FFT 你敢来这儿?

多项式是啥?详见 《普通初中课程标准实验教科书 数学 七年级 上册》(人民教育出版社)。

生成函数就是将数列 \(\{a_n\}\)

\[G(x) = \sum_{k = 0}^{\infty}a_kx^k \]

但是不要被那个 \(\infty\)

而且我们在题里只需要知道前\(n\)项的系数就行了,本质上是用一个多项式来表示数列。

为什么要多项式表示呢?因为多项式有各种各样神仙操作,这就是我们下面要讲的各种东西。

为了充分利用多项式的神奇性质,我们需要知道这些东西:

  • 高一基础数学知识
  • 微积分基础
  • NTT

本文并不会涉及到这些多项式操作的组合意义是什么,主要讲的就是板子怎么写,也就是最基础的部分。

但是其中有些毒瘤操作可能并不会用上,一般来讲掌握 \(4 - 10\)

2. 微积分基础知识

别害怕,没什么难的。

主要讲一讲基础的部分,微积分是一门博大精深水深的学科,详细的部分大家会在大学学习,这里只讲一些皮毛。

2. 1 积分

记不记得高一时最开始学的物理?

没错就是运动学。

位移\(x\),速度\(v\),加速度\(a\)是什么关系?

我们来回顾一下匀加速实现运动:虽然速度 \(v = at\)

由于速度是不断变化,我们并没有什么显然的式子来算位移。

但如果我们将速度看做一段段的匀速运动,我们就可以用式子 \(x = vt\)

于是我们将速度分成 \(\Delta t\) 长的一段段,总时间为 \(t\) ,我们就有了 \(N = \frac {t}{\Delta t}\) 个时间段。而对于第 \(k\) 个时间段,我们将这段时间内的速度近似的看成 \(v_k = ak\Delta t\)

\[\begin {align} x=&\sum_{k = 1}^{N} v_k\Delta t \\ =& \sum_{k = 1}^{N} ak\Delta t^2 \\ =& a\Delta t^2\sum_{k = 1}^Nk \\ =& a\Delta t^2 \frac {N(N+1)}{2} \\ =& \frac 1 2 a\Delta t^2 \left(\left(\frac {t}{\Delta t}{}\right)^2 + \left(\frac {t}{\Delta t}{}\right)\right) \\ =& \frac 1 2 at^2 + \frac 1 2 at \Delta t \end {align} \]

然后当 $\Delta t $ 趋于\(0\)的时候,$x =\frac 1 2 at^2 $ 。怎么样,有没有很熟悉?还记得吗?

我们来思考一下, $\Delta t $ 趋于\(0\)是啥意思?我们将 \(t\) 分成了 $ \frac {t}{\Delta t}$ 个时间段, $\Delta t $ 趋于 \(0\) ,也就是说我们的段数趋近于无穷大。虽然 \(\Delta t\) 等于 \(0\) 的时候我们会浮点数例外,但是我们却不能否认当 \(\Delta t\) 越来越接近 \(0\)

以上过程实际上就是积分的过程,用更正规的表达方式来表示,就是:

\[x = \int_0^tv\mathrm d t = \frac 1 2 at^2 \]

在这里, \(x\) 和 \(v\) 都是一个关于时间 \(t\) 的函数。而 \(d\) 并没有实际含义,只是一个算子,表示我们的函数的自变量是 \(t\)

而积分的感性理解,就是求函数图像与 \(x\)

2. 2 导数

假如我们已经知道了位移的表达式,我们怎么知道速度和加速度的表达式呢?

我们来看速度的定义:\(v = \frac {\Delta x} {\Delta t}\) ,也就是位移函数的斜率。当位移是一个匀加速运动的时候,我们可以这样容易地计算出速度。但如果位移的表达式是 \(x =\frac 1 2 at^2\)

我们依旧将位移函数看成一段段的一次函数。我们取两个时间点 \(t,t_1\) ,满足 \(t_1 = t + \Delta t\)

\[\begin {align} v =& \frac {\Delta x} {\Delta t} \\ =& \frac {x_{t_1}-x_t} {t_1-t} \\ =& \frac 1 2 a \frac {t_1^2-t^2}{t_1 - t} \\ =& \frac 1 2 a \frac {(t_1-t)(t_1+t)}{t_1 - t} \\ =& \frac 1 2 a (t_1 + t) \end {align} \]

同样的,我们另 \(\Delta t\) 趋于 \(0\) ,这样 \(v = at\)

当 \(\Delta t\) 趋于 \(0\) 的时候,我们好像又浮点数例外了,然而还是那个极限的思想:虽然我们不能除 \(0\) ,但是这个极限却确实存在。所以这一点的斜率,我们就定义成这个极限。

(其实微积分中好多不合情理的东西最终都是个定义)。

于是我们也得到了导数的感性理解:函数图像上某一点的斜率。

2. 3 不定积分和微积分基本定理

刚才的积分是有上下界的,叫做定积分,它是一个

而如果没有上下界,叫做不定积分,它是一个函数

不定积分的过程,是已知一个函数 \(F(x)\) 的导数 \(f(x)\) ,来求 \(F(x )\) 。我们称 \(F(x)\) 为 \(f(x)\) 的原函数

(注意导数也是一个函数)。

我们接下来所说的多项式积分,是求不定积分,也就是求另一个多项式。

顺带提一下微积分基本定理:

\[\int_a^bf(x)\mathrm dx = F(b) - F(a) = F(x)\Large|_a^b \]

2. 4 基本函数求导公式

时间关系不再一一证明。

感兴趣的同学可以课下自己学习《微积分》还有b站3Blue1Brown的视频。

以下除了 \(x\) 之外的字母都是常数。 \(y\) 的导数表示成 \(y'\)

\[\begin {align} c' &= 0 \\ x^n {'} &= nx^{n-1} \\ a^x {'} &= a^x\ln a \\ e^x {'} &= e^x \\ \log_ax' &= \frac {1} {x \ln a} \\ \ln x ' &= \frac {1} {x} \\ \sin x' &= \cos x \\ \cos x' &= -\sin x \end {align} \]

2. 5 求导运算法则

\(f(x),g(x)\) 都表示关于 \(x\)

\[\begin {align} (f(x) \pm g(x))' &= f'(x) \pm g'(x) \\ (f(x)g(x))' &= f'(x)g(x) + f(x)g'(x) \\ \left(\frac{f(x)}{g(x)}\right)' &= \frac {f'(x)g(x) - f(x)g'(x)} {g(x)^2} \\ (f(g(x)))' &= f'(g(x))g'(x) \end {align} \]

2. 6 常用积分公式

只需要知道两个。

\[\begin {align} \int x^n\mathrm dx &= \frac {1} {n+1}x^{n+1} + C \ (n \ne -1)\\ \int \frac 1 x \mathrm dx &= \ln |x| + C \end {align} \]

因为求导的话会消掉常数项,所以不定积分是要带一个常数 \(C\) 的,但是我们一般当成 \(0\)。

积分运算法则和 $ \sum $ 一样(吧大概)。

2. 7 泰勒展开和泰勒级数

具体形式参见在美妙的数学王国中畅游

已知 \(f(x_0)\) ,我们可以这样近似 \(f( x )\)

( \(f^{(n)}(x)\) 表示 \(f(x)\) 的 \(n\) 阶导数,就是求导 \(n\)

\[f(x) = \sum_{n=0}^{\infty}\frac {f^{(n)}(x_0)(x-x_0)^n} {n!} \]

特别的,当 \(x_0 = 0\)

3. 牛顿迭代

以下所有东西都能用这个东西推。

假如我们现在有一个多项式 \(f(x)\) 和一个方程 \(g(f(x)) = 0\) ,我们已知 \(f(x)\) 的前 \(n\) 项 \(f_0(x)\) ,要求 \(f(x)\) 的前 \(2n\)

牛顿迭代就是用来解这个方程的。

我们有

\[f(x) \equiv f_0(x) \mod x^n \]

我们对 \(g(f(x))\)

\[\begin {align} 0 &= g(f(x)) \\ &= g(f_0(x)) + g'(f_0(x))(f(x)-f_0(x))+\frac 1 2g''(f_0(x))(f(x)-f_0(x))^2+\dots \\ &\equiv g(f_0(x)) + g'(f_0(x))(f(x)-f_0(x)) \mod x^{2n} \end {align} \]

因为 \(f(x)-f_0(x)\) 最低项的次数是 \(n\)

化一化式子,我们就得到了

\[f(x) \equiv f_0(x) - \frac{g(f_0(x))}{g'(f_0(x))} \mod x^{2n} \]

每一次迭代,项数翻倍,复杂度

\[T(n) = T(n/2) + O(n \log n) = O(n \log n) \]

4. 多项式求逆

以下模数默认 \(998244353\)。

已知 \(A(x)\) ,求 \(B(x)\) 满足 \(A(x)B(x) \equiv 1\)

由于不明原因,这里用牛顿迭代解释不太科学。考虑这样构造。

\[\begin {align} A(x)B(x) &\equiv 1 \mod x^n\\ A(x)B(x) - 1 &\equiv 0 \mod x^{n} \\ (A(x)B(x) - 1) ^ 2 &\equiv 0 \mod x^{2n} \\ A(x)(2B(x) - A(x)B(x)^2) &\equiv 1 \mod x^{2n} \\ \end {align} \]

5. 多项式求导

按照定义即可。

\[A'(x) = \sum_{k \geq 1}ka_kx^{k-1} \]

6. 多项式积分

按照定义即可。

\[\int A(x) = \sum_{k \geq 0}\frac {a_k x^{k+1}} {k+1} \]

7. 多项式对数函数

这个用不着迭代。

求 $B(x) = \ln(A(x)) $ ,则 \(B'(x)=\frac {A'(x)}{A(x)}\)

然后多项式求导求逆加积分就好了。

8. 多项式指数函数

设 \(B(x) = e^{A(x)}\),

构造 \(g(B(x)) = \ln(B(x)) - A(x) = 0\)。

直接套公式 \(B(x) = B_0(x) + B_0(x)(A(x) - \ln(B_0(x))\)。

注意这里要保证 \(A(x)\) 的常数项 为 \(0\) 。不然你从哪儿找到一个 \(e^c\)

9. 多项式快速幂

先整体除常数项,算完后乘常数项的\(k\)次幂就好啦!

为什么要除呢?因为 \(\ln\) 要保证常数项为 \(1\)

$A(x)^k = (e^{\ln A(x)})^k = e^{k\ln A(x)} $ 。

10. 多项式开方

你可以选择牛顿迭代推式子,你也可以选择用上面的快速幂,也就是 \(\text{Inv}2\)

那么 \(\text{Inv2}\)

其实是模 \(MOD\) 意义下而不是 \(\varphi(MOD) = MOD - 1\)

虽然在指数上,可这只是一个记号而已。你并没有找到一个 \(e\) ,然后算 \(e\)

你实际上算的是这样一个函数

\[\exp(F(x)) = \sum_{k = 0}^\infty \frac{F(x)^i}{i!} \]

所以 \(k\) 并没有在指数上,自然也就不是模 \(MOD - 1\) 而是模 \(MOD\)

同理,如果你能处理常数项的问题,也就是解 \(k\)

11. 多项式带余除法

这和求逆不太一样,要求给出一个商多项式和一个余多项式。

大概的思想就是将余多项式搞没,然后算出商,最后再算出来余多项式。

这里设

\[\begin{align} F(x) = \sum_{i = 0}^nf_ix^i \\ G(x) = \sum_{i = 0}^mg_ix^i \\ \end{align} \]

然后要求你算出 \(F(x) = G(x) *Q(x) + P(x)\)

这里假设 \(n \geq m\) , \(Q(x)\) 的次数为 \(n - m\) ,\(P(x)\) 的次数为 \(m - 1\)

考虑这样一个东西

\[F_R(x) = x^nF(\frac{1}{x}) \]

我们发现 \(F_R(x)\) 实际上就是把 \(F(x)\)

那我们尝试推一下式子。

\[\begin {align} F(x) &= Q(x) * G(x) +P(x) \\ F(\frac 1 x) &= Q(\frac 1 x) * G(\frac 1 x) + P(\frac 1 x) \\ x^nF(\frac 1 x) &= x^{n-m}Q(\frac 1 x) * x^mG(\frac 1 x) + x^{n-m+1}x^{m-1}P(\frac 1 x) \\ x^nF(\frac 1 x) &= x^{n-m}Q(\frac 1 x) * x^mG(\frac 1 x) + x^{n-m+1}x^{m-1}P(\frac 1 x) \mod x^{n-m+1} \\ x^nF(\frac 1 x) &= x^{n-m}Q(\frac 1 x) * x^mG(\frac 1 x) \mod x^{n-m+1} \\ F_R(x) &= Q_R(x) * G_R(x) \mod x^{n - m + 1} \\ \end {align} \]

然后我们普通的求逆求出来 \(Q_R(x)\) 之后,std::reverse 就能得到 \(Q(x)\)

\[P(x) = F(x) - Q(x)*G(x) \]

我们也就可以得到 \(P(x)\)

12. 多项式三角函数

\(\sin\) 和 \(\cos\)

这个东西真的一点用都没有。但是我们就是能算。

首先根据泰勒展开我们有

\[\begin{align} \exp(ix) = \cos(x) + i\sin(x) \\ \exp(-ix) = \cos(x) - i\sin(x) \end{align} \]

(其实这就是欧拉公式的由来)。

所以

\[\begin{align} \sin(x) = \frac{\exp(ix) - \exp(-ix)}{2i} \\ \cos(x) = \frac{\exp(ix) + \exp(-ix)}{2} \end{align} \]

给读进来的多项式乘个 \(i\) ,\(\exp\)

那么哪里来的 \(i\)

如果你像我一样傻,你会暴力试所有数找一个平方等于 \(998244352\)

然而 \(i\) 不就是个 \(4\)

qpow(3, (MOD - 1) / 4)

没了。

\(\arcsin\) 和 \(\arctan\)。

这玩意比上面那俩还没用。不过更好写。

根据高数内容,

\[\arcsin(x)' = \frac{1}{\sqrt{1-x^2}} \\ \arctan(x)' = \frac{1}{1+x^2} \\ \]

然后

\[\begin{align} \arcsin(A(x)) = \int \frac{A(x)'}{\sqrt{1-A(x)^2}} \\ \arctan(A(x)) = \int \frac{A(x)'}{1+A(x)^2} \\ \end {align} \]

没了。

13. 多项式多点求值

多点求值,就是给你一个多项式 \(A(x)\) 和一堆 \(x\) 坐标让你求对应的 \(y\)

构造多项式

\[F_{l,r}(x) = \prod_{i=l}^r(x - x_i) \]

然后让 \(A(x)\) 模 \(F_{l,r}\) ,得到一个余式 \(R(x)\)

\[A(x) = Q(x) F_{l,r}(x) + R(x) \]

然后发现当 \(x\) 取 \(x_l\) 到 \(x_r\)

然而暴力模每个 \(F_{l,l}\) 复杂度并不对。于是我们可以先模 \(F_{l,r}\) ,然后把模剩下的多项式分别模 \(F_{l,mid},F_{mid+1,r}\) ,递归处理。每次取模时,余式的次数会变为模多项式的次数减一,于是每次次数减半,复杂度 \(O(n\log^2n)\)

实现时分治加 NTT 求 \(F\) ,然后用一个线段树一样的数组结构存下来每一个 \(F_{l,r}\)

Poly mem[MAXN];
inline void Divide(int u, int l, int r, const Poly& A) {
  if (l == r) {
    mem[u] = Poly({MOD - A[l], 1});
    return;
  }
  int mid = (l + r) >> 1;
  Divide(u << 1, l, mid, A), Divide(u << 1 | 1, mid + 1, r, A);
  mem[u] = mem[u << 1] * mem[u << 1 | 1];
}

inline void Eval_recur(int u, int l, int r, const Poly& A, Poly& res) {
  if (l == r) return res.push_back(A[0]);
  int mid = (l + r) >> 1;
  Eval_recur(u << 1, l, mid, Div(A, mem[u << 1]).second, res);
  Eval_recur(u << 1 | 1, mid + 1, r, Div(A, mem[u << 1 | 1]).second, res);
}

inline Poly Multieval(const Poly& A, const Poly& B) {
  Divide(1, 0, B.size() - 1, B);
  Poly res;
  Eval_recur(1, 0, B.size() - 1, Div(A, mem[1]).second, res);
  return res;
}

14. 多项式快速插值

快速插值,就是 NTT 优化拉格朗日插值。

先看拉格朗日插值的式子

\[F(x)=\sum_{i=1}^n{\frac{\prod_{{j}\neq{i}}{({x}-{x_j})}}{\prod_{{j}\neq{i}}{({x_i}-{x_j})}}{y_i}} \]

分母是个常数,单独和 \(y\)

\[F(x)=\sum_{i=1}^n{\frac{y_i}{\prod_{{j}\neq{i}}{({x_i}-{x_j})}}\prod_{{j}\neq{i}}{({x}-{x_j})}} \]

如果我们手头有一个

\[G(x) = \prod_{}{({x}-{x_i})} \]

那么我们就需要求出来这个多项式

\[\frac{G(x)}{(x - x_i)} \]

在 \(x_i\)

肯定不能每次都除啊,复杂度原地爆炸。

我们考虑直接把 \(x_i\) 代进去,就成了 \(\frac{0}{0}\)

这时直接用洛必达法则求极限就是正确值。

什么是洛必达法则?

当你需要求 \(\frac{F(x)}{G(x)}\) 在某一点 \(x_0\) 的极限的时候,很不巧,变成了 \(\frac 0 0\) 或者 \(\frac \infty \infty\)

我们就给分子分母同时求导,一直导到分子分母不是 \(\frac \infty \infty\) 和 \(\frac 0 0\) 为止。这个时候极限就是 \(\frac{F(x_0)}{G(x_0)}\)

然后我们给上面那个式子洛必达一下,得到 \(G'(x_i)\)

直接上多点求值就能把所有的 \(\frac{y_i}{\prod_{{j}\neq{i}}{({x_i}-{x_j})}}\)

后面的那个 \(\prod_{{j}\neq{i}}{({x}-{x_j})}\)

具体可以看代码实现。用到了一些上面多点求值的函数。

inline Poly Insv_recur(int u, int l, int r, const Poly& A) {
  if (l == r) return Poly({A[l]});
  int mid = (l + r) >> 1;
  Poly a = Insv_recur(u << 1, l, mid, A) * mem[u << 1 | 1];
  Poly b = Insv_recur(u << 1 | 1, mid + 1, r, A) * mem[u << 1];
  return a + b;
}

inline Poly Fastinsv(const Poly& X, const Poly& Y) {
  Divide(1, 0, X.size() - 1, X);
  Poly A = Derivate(mem[1]), B;
  Eval_recur(1, 0, X.size() - 1, A, B);
  for (size_t i = 0; i < X.size(); ++i)
    B[i] = mul(qpow(B[i], MOD - 2), Y[i]);
  return Insv_recur(1, 0, X.size() - 1, B);
}

讲完了。

15. 拉格朗日反演

又是这拉格朗日。

这里我们需要引入一个 “复合逆” 的概念。

“复合逆” 和反函数差不多。当 \(F(G(x)) = 1\) 的时候, \(F(x)\) 和 \(G(x)\)

我们现在的任务就是求一个多项式的复合逆。

然而不幸的是,我们并没有复杂度低于 \(O(n^2)\)

但是我们可以快速求得某一项的取值。

限于作者水平不能给出证明。这里只给出计算方法。

\[[x^n] g(x) = \frac 1 n [x^{n-1}]\left(\frac {x}{f(x)}\right)^n \]

而且我们还有广义拉格朗日反演

\[[x^n]h(g(x))=\frac 1 n[x^{n-1}]h'(x)\left(\frac x {f(x)}\right)^n \]

16. 分治 NTT

当卷积变成一种“我卷我自己”的形式的时候,我们可以考虑使用 CDQ 分治来求。

就是转移式成了这个样子

\[f_i = \sum_{j = 1}^i f_{i - j}g_j \]

我们就可以考虑先递归计算前一半,然后计算前一半对后一半的贡献,然后去递归后一半。

void CDQ(Poly& F, Poly& G, int l, int r) {
    if (l == r) {
        if (!l) F[l] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    CDQ(F, G, l, mid);
    Poly A(F.begin() + l, F.begin() + mid + 1);
    Poly B(G.begin(), G.begin() + r - l + 1);
    A = A * B;
    for (int i = mid + 1; i <= r; ++i) 
        F[i] = add(F[i], A[i - l]);
    CDQ(F, G, mid + 1, r);
}

17. 完结撒花

大概是完了。

给个代码实现。

std::vector<int> 实现多项式全家桶其实并没有特别慢,跑的还是挺快的。而且代码看上去很舒服,不会特别乱糟糟的一大片。

小范围内暴力卷积会比 \(FFT\) 快不少。小范围大概就是 \(2000\)

#include <bits/stdc++.h>

inline int read() {
  int ret, cc, sign = 1;
  while (!isdigit(cc = getchar()))
    sign = cc == '-' ? -1 : sign;
  ret = cc - 48;
  while (isdigit(cc = getchar()))
    ret = cc - 48 + ret * 10;
  return ret;
}

const int MOD = 998244353;
const int G = 3;
const int MAXN = 600010;
typedef std::vector<int> Poly;

inline int add(int a, int b) { return (a += b) >= MOD ? a - MOD : a; }
inline int sub(int a, int b) { return (a -= b) <    0 ? a + MOD : a; }
inline int mul(int a, int b) { return 1ll * a * b % MOD; }
inline int qpow(int a, int p) {
  int ret = 1;
  for (p += (p < 0) * (MOD - 1); p; p >>= 1, a = mul(a, a))
    if (p & 1) ret = mul(ret, a);
  return ret;
}

inline Poly operator * (const Poly&, const Poly&);
inline Poly operator + (const Poly&, const Poly&);
inline Poly operator - (const Poly&, const Poly&);
inline Poly Inverse(const Poly&);
inline Poly Derivate(const Poly&);
inline Poly Integral(const Poly&);
inline Poly Ln(const Poly&);
inline Poly Exp(const Poly&);
inline Poly Pow(const Poly&, int);
inline Poly Sqrt(const Poly&);
inline Poly Multieval(const Poly&, const Poly&);
inline Poly Fastinsv(const Poly&, const Poly&);
inline std::pair<Poly, Poly> Div(const Poly&, const Poly&);
inline void Read(Poly&);
inline void Print(const Poly&);

int inv[MAXN];
int fac[MAXN];

int main() {

}

inline void Read(Poly& A) {
  std::generate(A.begin(), A.end(), read);
}

inline void Print(const Poly& A) {
  for (auto x : A) printf("%d ", x);
  puts("");
}

int rev[MAXN];
int W[MAXN];

inline int getrev(int n) {
  int len = 1, cnt = 0;
  while (len < n) len <<= 1, ++cnt;
  for (int i = 0; i < len; ++i)
    rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1));
  return len;
}

inline void NTT(Poly& a, int n, int opt) {
  a.resize(n);
  for (int i = 0; i < n; ++i)
    if (i < rev[i])
      std::swap(a[i], a[rev[i]]);
  for (int i = (W[0] = 1); i < n; i <<= 1) {
    int wn = qpow(G, opt * (MOD - 1) / (i << 1));
    for (int k = i - 2; k >= 0; k -= 2)
      W[k + 1] = mul(W[k] = W[k >> 1], wn);
    for (int j = 0, p = i << 1; j < n; j += p) {
      for (int k = 0; k < i; ++k) {
        int x = a[j + k], y = mul(W[k], a[j + k + i]);
        a[j + k] = add(x, y), a[j + k + i] = sub(x, y);
      }
    }
  }
  if (opt == -1)
    for (int i = 0, r = qpow(n, MOD - 2); i < n; ++i)
      a[i] = mul(a[i], r);
}

inline Poly operator * (const Poly& lhs, const Poly& rhs) {
  if (lhs.size() * rhs.size() <= 2000) {
    Poly A(lhs.size() + rhs.size() - 1);
    for (size_t i = 0; i < lhs.size(); ++i)
      for (size_t j = 0; j < rhs.size(); ++j)
        A[i + j] = add(A[i + j], mul(lhs[i], rhs[j]));
    return A;
  }
  Poly A(lhs), B(rhs);
  int len = A.size() + B.size() - 1, bln = getrev(len);
  NTT(A, bln, 1), NTT(B, bln, 1);
  for (int i = 0; i < bln; ++i)
    A[i] = mul(A[i], B[i]);
  NTT(A, bln, -1), A.resize(len);
  return A;
}

inline Poly operator + (const Poly& lhs, const Poly& rhs) {
  Poly C(std::max(lhs.size(), rhs.size()));
  for (size_t i = 0; i < C.size(); ++i)
    C[i] = add(i < lhs.size() ? lhs[i] : 0, i < rhs.size() ? rhs[i] : 0);
  return C;
}

inline Poly operator - (const Poly& lhs, const Poly& rhs) {
  Poly C(std::max(lhs.size(), rhs.size()));
  for (size_t i = 0; i < C.size(); ++i)
    C[i] = sub(i < lhs.size() ? lhs[i] : 0, i < rhs.size() ? rhs[i] : 0);
  return C;
}

inline Poly Inverse(const Poly& A) {
  Poly B(1, qpow(A[0], MOD - 2));
  int n = A.size() << 1;
  for (int i = 2; i < n; i <<= 1) {
    Poly C(A); C.resize(i);
    int len = getrev(i << 1);
    NTT(B, len, 1), NTT(C, len, 1);
    for (int j = 0; j < len; ++j)
      B[j] = mul(B[j], sub(2, mul(B[j], C[j])));
    NTT(B, len, -1), B.resize(i);
  }
  B.resize(A.size());
  return B;
}

inline std::vector<int> getinv() {
  std::vector<int> inv(MAXN);
  inv[1] = 1;
  for (int i = 2; i < MAXN; ++i)
    inv[i] = mul(MOD - MOD / i, inv[MOD % i]);
  return inv;
}

std::vector<int> Inv = getinv();

inline Poly Derivate(const Poly& A) {
  Poly C(A.size() - 1);
  for (size_t i = 0; i < C.size(); ++i)
    C[i] = mul(i + 1, A[i + 1]);
  return C;
}

inline Poly Integral(const Poly& A) {
  Poly C(A.size() + 1);
  for (size_t i = 1; i < C.size(); ++i)
    C[i] = mul(Inv[i], A[i - 1]);
  return C;
}

inline Poly Ln(const Poly& A) {
  Poly C = Integral(Derivate(A) * Inverse(A));
  C.resize(A.size());
  return C;
}

inline Poly Exp(const Poly& A) {
  Poly B(1, 1);
  int n = A.size() << 1;
  for (int i = 2; i < n; i <<= 1) {
    Poly C(A);
    C.resize(i), B.resize(i);
    B = B * (Poly(1, 1) - Ln(B) + C);
  }
  B.resize(A.size());
  return B;
}

inline Poly Pow(const Poly& A, int k) {
  Poly C(Ln(A));
  for (size_t i = 0; i < C.size(); ++i)
    C[i] = mul(C[i], k);
  return Exp(C);
}

inline Poly Sqrt(const Poly& A) {
  Poly C(A);
  int c = A[0], ic = qpow(c, MOD - 2);
  for (size_t i = 0; i < C.size(); ++i)
    C[i] = mul(C[i], ic);
  c = sqrt(c), C = Pow(C, Inv[2]);
  for (size_t i = 0; i < C.size(); ++i)
    C[i] = mul(C[i], c);
  return C;
}

inline std::pair<Poly, Poly> Div(const Poly& lhs, const Poly& rhs) {
  if (rhs.size() > lhs.size()) return {Poly(1, 0), lhs};
  Poly A(lhs), B(rhs);
  int len = A.size() - B.size() + 1;
  std::reverse(A.begin(), A.end());
  std::reverse(B.begin(), B.end());
  A.resize(len), B.resize(len);
  A = A * Inverse(B);
  A.resize(len);
  std::reverse(A.begin(), A.end());
  B = lhs - rhs * A;
  B.resize(rhs.size() - 1);
  return {A, B};
}

Poly mem[MAXN];
inline void Divide(int u, int l, int r, const Poly& A) {
  if (l == r) {
    mem[u] = Poly({MOD - A[l], 1});
    return;
  }
  int mid = (l + r) >> 1;
  Divide(u << 1, l, mid, A), Divide(u << 1 | 1, mid + 1, r, A);
  mem[u] = mem[u << 1] * mem[u << 1 | 1];
}

inline void Eval_recur(int u, int l, int r, const Poly& A, Poly& res) {
  if (l == r) return res.push_back(A[0]);
  int mid = (l + r) >> 1;
  Eval_recur(u << 1, l, mid, Div(A, mem[u << 1]).second, res);
  Eval_recur(u << 1 | 1, mid + 1, r, Div(A, mem[u << 1 | 1]).second, res);
}

inline Poly Multieval(const Poly& A, const Poly& B) {
  Divide(1, 0, B.size() - 1, B);
  Poly res;
  Eval_recur(1, 0, B.size() - 1, Div(A, mem[1]).second, res);
  return res;
}


inline Poly Insv_recur(int u, int l, int r, const Poly& A) {
  if (l == r) return Poly({A[l]});
  int mid = (l + r) >> 1;
  Poly a = Insv_recur(u << 1, l, mid, A) * mem[u << 1 | 1];
  Poly b = Insv_recur(u << 1 | 1, mid + 1, r, A) * mem[u << 1];
  return a + b;
}

inline Poly Fastinsv(const Poly& X, const Poly& Y) {
  Divide(1, 0, X.size() - 1, X);
  Poly A = Derivate(mem[1]), B;
  Eval_recur(1, 0, X.size() - 1, A, B);
  for (size_t i = 0; i < X.size(); ++i)
    B[i] = mul(qpow(B[i], MOD - 2), Y[i]);
  return Insv_recur(1, 0, X.size() - 1, B);
}