多项式与生成函数

多项式与生成函数

多项式

有关多项式

定义

在数学中,几个单项式的和,叫做多项式 。多项式中的每个单项式叫做多项式的项,这些单项式中的最高项次数,就是这个多项式的次数。其中多项式中不含字母的项叫做常数项。——baidu

假设有 nn 次多项式 AA ,其可以表示为:

A=i=0naixiA = \sum_{i = 0} ^ n a_i x^i

其中 aia_i 表示第 ii 此项的系数。

运算

多项式也有其四则运算,有 nn 次多项式 AAmm 次多项式 BB

加法

A+B=i=0naixi+i=0mbixi=i=0max(n,m)(ai+bi)xiA + B = \sum_{i = 0} ^ n a_i x^i + \sum_{i = 0}^m b_i x^i = \sum_{i = 0} ^ {\max(n, m)} (a_i +b_i)x^i

新的多项式次数为 max(n,m)max(n,m)

减法

和加法相同,

AB=i=0naixii=0mbixi=i=0max(n,m)(aibi)xiA - B = \sum_{i = 0} ^ n a_i x^i - \sum_{i = 0}^m b_i x^i = \sum_{i = 0} ^ {\max(n, m)} (a_i -b_i)x^i

乘法

AB=i=0n+m(j+k=iajbk)xiA \ast B = \sum_{i = 0} ^ {n + m}(\sum_{j + k = i} a_j b_k) x ^i

新的多项式次数为 n+mn + m,直接展开复杂度为 O(n2)O(n^2),有更优的计算方式。

除法

详见多项式除法。

拉格朗日插值定理

n+1n + 1个点值即可确定一个nn次多项式。

假设我们有 n+1n + 1 个点值为 (xi,yi)(x_i, y_i)。我们可以构造出对应的多项式为,

f(x)=i=0nyiji(xxj)ji(xixj)f(x) = \sum_{i = 0} ^ n y_i \frac{\prod _{j \not = i}(x - x_j)}{\prod_{j \not = i}(x_i - x_j)}

复杂度为 O(n2)O(n^2),当点值连续时可以通过预处理阶乘优化到 O(n)O(n)

此时,

f(x)=i=0nyij=0n(xj)(xi)i!(ni)!(1)nif(x) = \sum_{i = 0} ^ n y_i \frac{\prod_{j = 0}^n(x - j)}{(x-i)i!(n-i)!(-1)^{n-i}}

不连续时有更快的多项式快速插值算法。

秦九昭算法

秦九昭算法可以用来进行单次 O(n)O(n)的多项式求值,对于 nn 次多项式 A=i=0naixiA = \sum_{i= 0} ^ n a_i x^i

将次式一步步展开为,

A=anxn+an1xn1+a2x2+a1x+a0=x(anxn1+a2x+a1)+a0=\begin{aligned} A &= a_n x^n +a_{n- 1} x ^ {n - 1} + \cdots a_2 x^2 +a_1 x + a_0 \\ &= x (a_n x^{n - 1} + \cdots a_2 x + a_1) + a_0 \\ &= \cdots \end{aligned}

最后拆成了多个一次式,直接计算即可。但是对于多点求值的时候需要更优的算法。

导数

导数(Derivative),也称为导函数值,又名微商。在数学中发挥着很重要的作用。

导数是函数的局部性质。一个函数在某一点的导数描述了这个函数在这一点附近的变化率。如果函数的自变量和取值都是实数的话,函数在某一点的导数就是该函数所代表的曲线在这一点上的切线斜率。导数的本质是通过极限的概念对函数进行局部的线性逼近。 ——––baidu

其实通俗的来讲导数就是一个函数的变化率,比较经典的就是二次函数求切线。

假设我们要求某一光滑曲线在 MM 点的切线,先在函数图像上取一点 NN,假设直线 MTMTMM 点的切线,当 NMT\angle {NMT} 无限小时此时的直线 MNMN 就已经成为切线,设直线 MNMN 的斜率为 tanϕ\tan \phi,有:

tanϕ=f(x)f(x0)xx0\tan \phi = \frac{f(x) - f(x_0)}{x-x_0}

NN逼近与MM时,

tanϕ=limxx0f(x)f(x0)xx0\tan \phi = \lim\limits_{x \to x_0} \frac{f(x) - f(x_0)}{x-x_0}

Δx=xx0\Delta x = x-x_0,所以

tanϕ=limx0f(x0+Δx)f(x0)Δx\tan \phi = \lim\limits_{x \to 0} \frac{f(x_0+\Delta x) - f(x_0)}{\Delta x}

此处 tanϕ\tan \phi 即为函数在 x0x_0 处导数的取值,现在引出导数真正的定义 。

假设函数 y=f(x)y = f(x) 在点 x0x_0 处的邻域有定义,当自变量 xxx0x_0 处取得增量 Δx\Delta x(仍然在邻域内),相应的函数取得增量 Δy=f(x0+Δx)f(x0)\Delta y = f(x_0+\Delta x) - f(x_0),如果 ΔyΔx\frac{\Delta y}{\Delta x}Δx0\Delta x \to 0 时的极限存在,称为函数 y=f(x)y = f(x)x0x_0处可导,它的导数可以写成

f(x0)=limx0f(x0+Δx)f(x0)Δxf'(x_0) = \lim\limits_{x \to 0} \frac{f(x_0+\Delta x) - f(x_0)}{\Delta x}

f(x0)f'(x_0) 也可以记成 dydx\frac{\mathrm{d}y}{\mathrm{d}x} 或者 df(x)dx\frac{\mathrm{d}f(x)}{\mathrm{d}x},另外函数在 x0x_0 处可导的充分必要条件是,函数在 x0x_0 处的左右两侧的导数都 必须存在,并且相等 。

常见函数的导数:

  1. f(x)=Cf(x) = CCC为常数,f(x)=0f'(x) = 0
  2. f(x)=xn,f(x)=nxn1f(x) = x^n, f'(x) = n x ^ {n - 1}
  3. f(x)=sinx,f(x)=cosxf(x) = \sin x, f'(x) = \cos x
  4. f(x)=cosx,f(x)=sinxf(x) = \cos x, f'(x) = -\sin x
  5. f(x)=ax,f(x)=axlnaf(x) = a^x , f'(x) = a^x \ln a
  6. f(x)=logax,f(x)=1xlnaf(x) = \log_ax , f'(x) = \frac{1}{x \ln a}
  7. f(x)=lnx,f(x)=1xf(x) = \ln x, f'(x) = \frac{1}{x}
  8. f[g(x)]=f[g(x)]g(x)f[g(x)]'= f'[g(x)]g'(x)
  9. [f(x)g(x)]=f(x)g(x)f(x)g(x)g(x)2[\frac{f(x)}{g(x)}]' = \frac{f'(x)g(x) - f(x)g'(x)}{g(x) ^ 2}
  10. (f(x)±g(x))=f(x)±g(x)(f(x) \pm g(x))' = f'(x) \pm g'(x)
  11. (f(x)g(x))=f(x)g(x)+f(x)g(x)(f(x)g(x))' = f'(x)g(x) + f(x)g'(x)

微分

首先思考一个简答的问题,一个正方形金属薄片边长为 xx,受温度变化影响边长增加了 Δx\Delta x,求其面积变化了多少。

设正方形面积为 AA ,这样我们就得到了一个函数 A=x2A = x^2,计算 ΔA\Delta A 为:

ΔA=(x0+Δx)2x02=2x0Δx+(Δx)2\Delta A = (x_0+ \Delta x)^2 - x_0^2 = 2x_0\Delta x + (\Delta x)^2

首先第一部分 2x0Δx2 x_0 \Delta x 为一个线性函数,第二部分中当 Δx0\Delta x \to 0 时,(Δx)2(\Delta x)^2是比
Δx\Delta x 高阶的无穷小,即 (Δx)2=ο(Δx)(\Delta x)^2 =\omicron(\Delta x)

好了增量 Δy\Delta y 现在已经可以表示为:

Δy=AΔx+ο(Δx)\Delta y = A \Delta x + \omicron(\Delta x )

其中 AA 是不依赖 Δx\Delta x 的常数,并且 Δy\Delta yAΔxA\Delta x 的差为:

ΔyAΔx=ο(Δx)\Delta y - A\Delta x =\omicron(\Delta x)

是比 Δx\Delta x 更高阶的无穷小,这样当 A0A \ne 0,且 Δx|\Delta x | 无穷小时,Δy\Delta y 就可以近似的表示为 Δy=AΔx\Delta y = A \Delta x

接下来给出定义 ,设函数 y=f(x)y = f(x) 在某区间内有定义,x0x_0x0+Δxx_0+ \Delta x 在这段区间内,如果函数的增量

Δy=f(x0+Δx)f(x0)\Delta y = f(x_0 + \Delta x) - f(x_0)

可以表示为

Δy=AΔx+ο(Δx)\Delta y = A \Delta x + \omicron(\Delta x)

其中 AA 是不依赖于 Δx\Delta x 常数,那么称函数 y=f(x)y = f(x)x0x_0 处是可微的,而 AΔxA\Delta x 叫做函数 y=f(x)y = f(x)x0x_0 处相当于自变量增量 Δx\Delta x微分(Differential) 记作 dydy ,即

dy=AΔx\mathrm{d}y = A \Delta x

积分

积分(integral) 是微积分学与数学分析的一个核心概念。通常分为定积分不定积分两种。(主要区别就是定积分得到的结果是一个数,不定积分得到的是函数)

tips:不定积分是求导运算的逆运算。 这一结论被称为微积分基本定理 (fundamental theorem of calculus)

直观的说,对于一个给定的正实数值函数,在一个实数区间上的定积分可以理解为在坐标平面上,由曲线,直线以及轴围围成的 曲边梯形的面积值(一种确定的实数值)

如果一个函数的积分存在,并且有限,就说这个函数是可积的。一般来说,被积函数不一定只有一个变量,积分域也可以是不同维度的空间,甚至是没有直观几何意义的抽象空间。对于只有一个变量 xx 的实值函数 ff 在闭区间 [a,b][a,b] 上的积分记作:

abf(x)dx\int _{a}^{b} f(x) \mathrm{d}x

其中的 dx\mathrm{d}x 就是积分变量。

黎曼积分(Riemann Integral),就是所说的正常积分,定积分。其求积分值的核心思想就是通过无限逼近来确定这个积分值,需要注意的是,如果 f(x)f(x) 去负值,则对应的面积值也为负值。

对于一个闭区间 [a,b][a,b]分割PP就是指在这一个区间中取一个有限的点列 a=x0<x1<x2<<xn=ba = x_0 < x_1 < x_2 < \cdots < x_n = b 每个子区间长度的最大值定义为 λ=max(xi+1xi)\lambda = \max(x_{i+1}-x_i)

定义取样分割为在进行分割 PP 后在每一个子区间 [xi,xi+1][x_{i},x_{i+1}] 中取出一点 xitixi+1x_i \le t_i \le x_{i+1},其区间长度最大值仍用 λ\lambda 来表示。

对于一个在区间 [a,b][a,b] 有定义的实值函数 ffff 关于取样分割 x0xn,t0tn1x_0 \cdots x_n , t_0 \cdots t_{n-1} 的黎曼和表示为:

i=0n1f(ti)(xi+1xi)\sum_{i=0}^{n-1}f(t_i)(x_{i+1}-x_i)

上述式子中的每一项是子区间长度 xi+1xix_{i+1}- x_i 与在 tit_i 处的 f(ti)f(t_i) 的乘积,其实直观的来说就是将一个曲线梯形分割成无限个小的图形将其面积相加。

看来需要一个更严格的定义,我们需要把 λ\lambda 趋近于0函数值才能更精确。

SS 是函数 ff 在闭区间 [a,b][a,b] 上的黎曼积分,当且仅当 ϵ>0,δ>0\forall \epsilon > 0, \exists \delta > 0,使得 x0xn,t0tn1\forall x_0 \cdots x_n , t_0 \cdots t_{n-1},只要它的子区间长度最大值 λδ\lambda \le \delta 就有:

i=0n1f(ti)(xi+1xi)S<ϵ\bigg| \sum_{i = 0}^{n-1} f(t_i)(x_{i+1} -x_i)-S \bigg| < \epsilon

也就是说,对于一个函数 ff,如果在闭区间 [a,b][a,b] 上无论如何取样分割,只要它的子区间长度最大值足够小,函数 ff 的黎曼和都会趋向一个确定的值,那么在闭区间 [a,b][a,b] 上的黎曼积分存在,并且定义为黎曼和的极限,这时候我们称函数 ff黎曼可积的。

还有一个非常重要的定理F(x)F(x)f(x)f(x)的不定积分f(x)f(x)F(x)F(x) 的导数;反之亦然。

根据以上定理,我们有:

abf(x)dx=F(b)F(a)\int_a^b f(x) \mathrm{d}x = F(b)-F(a)

常见积分公式:

  1. kdx=kx+c\int k \mathrm{d}x = kx+ckk为常数
  2. xadx=1a+1xa+1+c(a1)\int x^a \mathrm{d} x = \frac{1}{a + 1} x ^ { a + 1} + c (a \not = -1)
  3. 1xdx=lnx+c\int \frac{1}{x} \mathrm{d}x = \ln |x| + c
  4. axdx=axlna+c(a>0,a1)\int a^x \mathrm{d}x = \frac{a^x}{\ln a} + c (a > 0, a \not = 1)
  5. logaxdx=1lna(xlnxx)+c\int \log_a x \mathrm{d} x = \frac{1}{\ln_a} (x \ln x - x) + c

牛顿迭代

牛顿迭代可以用来求方程的近似的一个或者多个解,首先设方程函数 f(x)=mf(x) = m 可以将方程改为 g(x)=f(x)m=0g(x) = f(x) - m = 0 我们只需要求出 g(x)=0g(x) = 0 的解,就可以求出 f(x)=mf(x) = m 的解。

对于求解 f(x)=0f(x) = 0, 设 rr 是所求零点, 选取 x0x_0 作为 rr 的初始近似值,则我们可以过点 (x0,f(x0))(x_0, f(x_0)) 做曲线 y=f(x)y = f(x) 的切线 LL, 我们知道切线与 xx 轴有交点, 我们已知切线 LL 的方程为 y=f(x0)+f(x0)(xx0)y =f(x_0) + f'(x_0)(x - x_0) 此时LLxx 轴的交点为 x1=x0f(x0)f(x0)x_1 = x_0 - \frac{f(x_0)}{f'(x_0)} 然后再以 (x1,f(x1))(x_1, f(x_1)) 点作切线, 求出与 xx 轴的交点, 重复以上过程直到 f(xn)f(x_n) 无限接近与 00, 即可,其中nn 次迭代公式为

xn+1=xnf(xn)f(xn)x_{n + 1} = x_n - \frac{f(x_n)}{f'(x_n)}

泰勒展开

泰勒公式,也称泰勒展开式。是用一个函数在某点的信息,描述其附近取值的公式。如果函数足够平滑,在已知函数在某一点的各阶导数值的情况下,泰勒公式可以利用这些导数值来做系数,构建一个多项式近似函数,求得在这一点的邻域中的值。

就是用一个多项式函数去逼近一个给定的函数,但是必须逼近的时候从某个函数图像上的某个点展开。

nn 是一个正整数。如果定一个包含 aa 的区间上的函数 ffaan+1n + 1 次可导, 那么对于这个区间上的任意 xx 都有,

f(x)=i=0nf(i)(a)i!(xa)i+Ri(x)f(x) = \sum_{i = 0} ^ n \frac{f^{(i)}(a)}{i!}(x - a) ^ i + R_i(x)

其中的多项式称为函数在 aa 处的泰勒展开式, Ri(x)R_i(x) 是泰勒公式的余项是 (xa)i(x - a) ^ i 的高阶无穷小。

a=0a = 0 即可得到 f(x)=f(x)=i=0nf(i)(0)i!xi+Ri(x)f(x) = f(x) = \sum_{i = 0} ^ n \frac{f^{(i)}(0)}{i!}x ^ i + R_i(x)

多项式相关算法

快速傅里叶变换(FFT)

快速傅里叶变换,主要用于加速多项式乘法,对于两个多项式 AABB,FFT可以将 O(n2)O(n^2) 的朴素算法优化为 O(nlogn)O(n \log n)

单位根

以单位圆点为起点,单位园的 nn 等分点为终点,在单位圆上可以得到 nn 个复数,设幅角为正且最小的复数为 ωn\omega_n,称为 nn 次单位根,即

ωn=cos2πn+isin2πn\omega_n = \cos \frac{2 \pi}{n} + i \sin \frac{2 \pi}{n}

有欧拉公式

ωnk=cos2kπn+isin2kπn\omega_n ^ k = \cos \frac{2k \pi}{n} + i \sin \frac{2k \pi}{n}

特别的有 ωn0=ωnn=1\omega_n ^ 0 = \omega_n ^ n = 1

另外还有以下性质

  1. ωnk=e2πikn\omega _ n ^ k = e ^{\frac{2 \pi i k}{n}}
  2. ωdndk=ωnk\omega_{dn} ^ {dk} = \omega_n ^k
  3. ωnk=a+bi,ωnk=abi\omega_n ^k = a + bi, \omega _ n ^ {-k} = a - bi
  4. ωnk+n2=ωnk\omega_n ^{k +\frac{n}{2}} = - \omega_n ^ k

离散傅里叶变换(DFT)

离散傅里叶变换(DFT) 主要是利用分支思想根据一个 nn 次的多项式可以由 n+1n + 1 个点唯一确定

对于一个 nn 次多项式,

A=i=0naixiA = \sum_{i = 0} ^ n a_i x ^ i

其系数进行奇偶分类,得到

A0=a0+a2x+a4x2+A1=a1+a3x+a5x2+A_0 = a_0 +a_2 x + a_4 x^2 + \cdots \\ A_1 = a_1 +a_3 x + a_5 x^2 + \cdots \\

原来的式子变成了

A(x)=A0(x2)+xA1(x2)A(x) = A_0(x ^ 2) + x \cdot A_1(x ^ 2)

然后将 ωnk\omega_ n ^kωnk+n2\omega_n ^ {k + \frac{n}{2}} 带入得

A(ωnk)=A0(ωn2k)+ωnkA1(ωn2k)A(ωnk+n2)=A0(ωn2k)ωnkA1(ωn2k)A(\omega_n ^ k) = A_0(\omega_n ^ {2k}) + \omega_n ^ k \cdot A_1(\omega_n ^ {2k}) \\ A(\omega_n ^ {k + \frac{n}{2}}) = A_0(\omega_n ^ {2k}) - \omega_n ^ {k} \cdot A_1(\omega_n ^ {2k})

可以发现两个式子只有常数不一样,也就是说只要知道了,[0,k1][0, k - 1] 内的点值就可以得到 [n2,k+n21][\frac{n}{2}, k +\frac{n}{2} - 1] 之间的点值。

时间复杂度为 O(nlogn)O(n \log n)

离散傅里叶逆变换(IDFT)

离散傅里叶变换(IDFT) ,可以将点值快速转化为系数,从而得出系数多项式。

需要用到单位根反演:

1ni=0n1ωnix=[xmodn=0]\frac{1}{n} \sum_{i = 0} ^ {n - 1} \omega_n ^ {ix} = [x \bmod n = 0]

证明:

由于 ωnix=ωn(i1)x×ωnx\omega _ n ^ {ix} = \omega_n ^ {(i-1)x} \times \omega_n ^ x

所以 ωnix\omega_n ^ {ix} 为等比数列,

1ni=0n1ωnix={1ni=0n11i=1xmodn=01n×1ωnnx1ωnx=0xmodn0\frac{1}{n} \sum_{i = 0} ^ {n - 1} \omega_n ^ {ix} = \left\{ \begin{aligned} &\frac{1}{n} \sum_{i = 0} ^ {n - 1} 1 ^ i = 1 &x \bmod n = 0 \\ &\frac{1}{n} \times \frac{1 - \omega_n ^ {nx}}{1 - \omega_n ^ x} = 0 & x \bmod n \not = 0 \end{aligned} \right.

接下来就是IDFT的过程:将对应点值共轭带入求出后除以 lenlen 即可。

递归实现

由于需要用到复数需要自己封装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void FFT(Complex *a, int len, int type)
{
if(len == 1)return;
Complex a1[(len >> 1) + 10], a2[(len >> 1) + 10];
for(int i = 0; i <= len; i += 2)
a1[i >> 1] = a[i], a2[i >> 1] = a[i + 1];
FFT(a1, len >> 1, type);
FFT(a2, len >> 1, type);
Complex x = Complex(cos(2.0 * pi / len), type * sin(2.0 * pi / len));
Complex w = Complex(1, 0);
for(int i = 0; i < (len >> 1); i++, w = w * x)
{
a[i] = a1[i] + w * a2[i];
a[i + (len >> 1)] = a1[i] - w * a2[i];
}
}

最后需要除以长度,但是递归实现。

蝴蝶优化

观察分治的过程最后的结果其实就是对应二进制的反转,可以对此对分治进行优化。

预处理一个’rev’数组,

1
2
for(int i = 0; i <= len; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1));

然后FFT的过程就变成了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void FFT(Complex *a, int len, int type)
{
for(int i = 0; i < len; i++)
if(i < rev[i])swap(a[i], a[rev[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;
}

快速数论变换(NTT)

快速数论变换(NTT) 相比于FFT虽然时间复杂度均为 O(nlogn)O(n \log n),但是FFT会出现精度问题,不好解决取模,NTT可以很好的解决这个问题,但是常数会大。

原根

原根定义为:设 mm 为正整数,aa 是整数,若 amodma \bmod m 的阶等于 φ(m)\varphi (m),则称 aamodm\bmod m 的一个原根。

原根有一个很重要的性质可以支持像FFT中单位根一样的运算,即:若 PP 为素数, 假设一个数 ggPP 的原根, 那么 gimodPg^i \bmod P 的结果两两不同。

可以得到:

ωngp1n(modp)\omega_n \equiv g^{\frac{p - 1}{n}} \pmod p

然后我们就可以将FFT中的 ωn\omega _n 替换为 gp1ng^{\frac{p - 1}{n}}

但是注意的是NTT对模数有要求,其模数必须要满足原根的定义,否则是不能使用NTT的,比如 998244353998244353 就为NTT模数, 其原根为 33

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
void NTT(int *a, int len, int type)
{
for(int i = 0; i < len; i++)
if(i < rev[i])swap(a[i], a[rev[i]]);
for(int k = 1; k < len; k <<= 1)
{
int x = qpow(type == 1 ? g : gi, (mod - 1) / (k << 1));
for(int i = 1; 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 iv = qpow(len, mod - 2);
for(int i = 0; i < len; i++)
a[i] = a[i] * iv % mod;
}
}

快速沃尔什变换(FWT)

给定两个长度为 2n2 ^ n 的两个序列 A,BA,B, 求序列 CC

Ci=jk=iAj×BkC_i = \sum_{j \oplus k = i} A_j \times B_k

其中 \oplus 表示位运算与,或,异或。

或运算

首先求序列 FWT[A]=i=ijAjFWT[A] = \sum_{i = i | j} A _ j,来求出满足条件的 ii 的子集,显然会有

Ci=i=jkAj×BkFWT[C]=FWT[A]×FWT[B]C_i = \sum_{i = j | k} A_j \times B_k \Rightarrow FWT[C] = FWT[A] \times FWT[B]

接下来就是考虑如何进行 FWTFWT 运算, 有

FWT[A]=merge(FWT[A0],FWT[A0]+FWT[A1])IFWT[A]=merge(IFWT[A0],IFWT[A1]FWT[A0])FWT[A] = merge(FWT[A_0], FWT[A_0] + FWT[A_1]) \\ IFWT[A] = merge(IFWT[A_0], IFWT[A_1] - FWT[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;
}
}
}
}

与运算

同或运算,有

FWT[A]=merge(FWT[A0]+FWT[A1],FWT[A1])IFWT[A]=merge(IFWT[A0]IFWT[A1],IFWT[A1])FWT[A] = merge(FWT[A_0] + FWT[A_1], FWT[A_1])\\ IFWT[A] = merge(IFWT[A_0] - IFWT[A_1], IFWT[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;
}
}
}
}

异或运算

推导得

FWT[A]=merge(FWT[A0]+FWT[A1],FWT[A0]FWT[A1])IFWT[A]=merge(IFWT[A0]+IFWT[A1]2,IFWT[A0]IFWT[A1]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}) \\

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对模数是有要求的其必须满足原根的相关定义,模数必须可以写成 a2k+1a \cdot 2 ^ k + 1 的形式。

469762049=7×226+1(g=3)998244353=119×223+1(g=3)1004535809=479×221+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)

如果题目中模数为 1e9+71e9 + 7,那么NTT就会受到限制,然后就可以使用任意模数NTT,(也可以称为三模数NTT),

计算时可以先找三个大质数, 分别计算结果,然后用中国剩余定理CRT合并即可。

首先记三次NTT的结果为:

ansa1(modp1)ansa2(modp2)ansa3(modp3)ans \equiv a_1 \pmod {p_1} \\ ans \equiv a_2 \pmod {p_2} \\ ans \equiv a_3 \pmod {p_3}

先合并前两个得到:

ansa4(modp1p2)ans \equiv a_4 \pmod {p_1 p_2}

将其转化为等式为:

ans=kp1p2+a4ans = k p_1 p_2 + a_4

接着求 kk

k=(a3a4)p11p21(modp3)k = (a_3 - a_4)p_1^{-1} p_2 ^ {-1} \pmod {p_3}

所以:

anskp1p2+a4(modp1p2p3)ans \equiv kp_1 p_2 + a_4 \pmod {p_1p_2p_3}

拆系数FFT

拆系数FFT也可以用来解决任意模数多项式乘法,由于普通的FFT精度误差大,需要进行优化,考虑将一个较大的数拆成两个较小的数,有 nn 次多项式 AAmm 次多项式 BB,

A=i=0naixiB=i=0mbixiA = \sum_{i = 0} ^ n a_i x ^ i \\ B = \sum_{i = 0} ^ m b_i x ^ i \\

假设有一实数 PP,将多项式的系数拆为

A=i=0n(x1p+y1)xiB=i=0m(x2p+y2)xiA = \sum_{i = 0} ^ n (x_1 \sqrt p + y_1) x ^ i \\ B = \sum_{i = 0} ^ m (x_2 \sqrt p + y_2) x ^ i \\

再次拆解成两个多项式,

A=i=0nx1pxi+i=0ny1xi=pC1+D1B=i=0mx2pxi+i=0my2xi=pC2+D2A = \sum_{i = 0} ^ n x_1 \sqrt p x ^ i + \sum_{i = 0} ^ n y_1 x ^ i = \sqrt p C_1 + D_1 \\ B = \sum_{i = 0} ^ m x_2 \sqrt p x ^ i + \sum_{i = 0} ^ m y_2 x ^ i = \sqrt p C_2 + D_2 \\

进行多项式乘法可得,

AB=(pC1+D1)(pC2+D2)=pC1C2+p(C1D2+C2D1)+D1D2A \ast B = (\sqrt p C_1 + D_1) \ast (\sqrt p C_2 + D_2) = pC_1C_2 + \sqrt p (C_1D_2 + C_2D_1) + D_1D_2

由于多次进行FFT常数极大,不过有些式子可以合并,降低FFT次数。

多项式乘法逆

定义多项式 F1F ^ {-1} 为多项式 FF 的乘法逆元,满足

FF11(modxn)F \ast F ^ {-1} \equiv 1 \pmod {x^n}

假设我们已得知 FG1(modxn2),FG1(modxn)F \ast G' \equiv 1 \pmod {x ^ \frac{n}{2}}, F \ast G \equiv 1 \pmod {x ^ n}

可以推出,

FG1(modxn)GG0(modxn)(GG)20(modxn)F \ast G \equiv 1 \pmod {x ^ n} \\ G - G' \equiv 0 \pmod {x ^ n} \\ (G - G') ^ 2 \equiv 0 \pmod {x^n}

进一步展开得,

G22GG+G20(modxn)G'^ 2 - 2GG' + G ^ 2 \equiv 0 \pmod {x ^ n}

然后两边同时卷上 FF

FG22G+G0(modxn)FG'^2 - 2G' + G \equiv 0 \pmod {x ^ n}

得出

G2GFG2(modxn)G \equiv 2G' - FG'^ 2 \pmod {x ^ n}

可以递归实现,时间复杂度 O(nlogn)O(n \log n)

多项式对数函数

定义多项式对数函数为

G=ln(F)(modxn)G = \ln (F) \pmod {x ^ n}

假设有多项式 FF 和多项式 GG, 记 G=lnF(modxn)G = \ln F \pmod {x ^ n}

G(lnF)(modxn)G(lnF)F(modxn)GFF(modxn)G' \equiv (\ln F)' \pmod {x ^ n} \\ G' \equiv (\ln' F) F' \pmod {x ^ n} \\ G' \equiv \frac{F'}{F} \pmod {x ^ n}

需要注意的是这种方法需要保证 F0=1F_0 = 1

多项式指数函数

定义多项式指数函数为

G(x)=eF(x)(modxn)G(x) = e ^ {F(x)} \pmod {x ^ n}

牛顿迭代

牛顿迭代用于求函数零点,通过不断地切线逼近所求值,但最终也只是近似值,迭代的次数越多,精确度越高,误差越小。

假如我们要对一个非常大的数 aa 开方,手算,利用牛顿法来解决这个问题,其实本质上是求得 f(x)=x2af(x) = x ^2 - a 精确到整数得零点,假设我们已经求得了一个近似值 x0x_0,那么我们只需要过 (x0,f(x0))(x_0, f(x_0)) 这个点, 作这个函数图像的切线,取切线与 xx 轴的交点作为新的 x0x_0

假设我们要求一个函数 f(x)f(x) 的零点, 初始近似值是 x0x_0,则切线方程为

y=f(x0)(xx0)+f(x0)y = f'(x_0)(x - x_0) + f(x_0)

y=0y = 0,得到 x=x0f(x0)f(x0)x = x_0 - \frac{f(x_0)}{f'(x_0)}

假设我们现在要求 F(G(x))0F(G(x)) \equiv 0,然后利用上面的式子每一次令

G(x)=G0(x)F(G0(x))F(G0(x))G(x) = G_0(x) - \frac{F(G_0(x))}{F'(G_0(x))}

然后就可以很快的逼近真实值。

接下来推一下多项式exp

B(x)eA(x)(modxn)lnB(x)A(x)0(modxn)B(x) \equiv e ^ {A(x)} \pmod {x ^ n} \\ \ln B(x) - A(x) \equiv 0 \pmod {x^ n}

现在问题变为了使得 F(G(x))=lnG(x)A(x)0F(G(x)) = \ln G(x) - A(x) \equiv 0

然后求导,

F(G0(x))=1G0(x)F'(G_0(x)) = \frac{1}{G_0(x)}

然后接着带入上面牛顿迭代的式子,

G(x)=G0(x)(1lnG0(x)+A(x))G(x) = {G_0(x)(1 - \ln G_0(x) + A(x))}

每次迭代,使用多项式求 ln\ln,然后再做一遍多项式乘法,然后就可以得到答案,时间复杂度 O(nlogn)O(n \log n)
需要保证F0=0F_0 = 0

多项式开根

多项式开根用来解决

G2(x)F(x)(modxn)G^2(x) \equiv F(x) \pmod {x^n}

假设我们有 G2(x)F(x)(modxn2),H(G(x))=G2(x)FG'^2(x) \equiv F(x) \pmod {x ^ {\frac{n}{2}}}, H(G(x)) = G^2(x) - F,求 G2(x)F(x)(modxn)G^2(x) \equiv F(x) \pmod {x ^ n}

G2(x)F(x)modxn2,G2(x)F(x)(modxn2)G2(x)F0(modxn2)H(G)0(modxn2)GGH(G)H(G)(modxn)GG2+F2G(modxn)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}

需要保证 F0=1F_0 = 1

多项式幂函数

多项式幂函数是用来解决

G(x)(F(x))kmodxnG(x) \equiv (F(x)) ^ k \mod x ^ n

先求一遍 ln\ln 然后乘以 kk 再使用 exp\exp,就好啦。

需保证F0=1F_0 = 1

多项式的一些普通情况

多项式求ln

不保证 F0=1F_0 = 1。不存在,有定理:

在模意义下当且仅当 F0=1F_0 = 1F(x)F(x) 有对数多项式问题。

多项式求exp

不保证 F0=0F_0 = 0 。同多项式求 ln\ln

多项式开根

不保证 F0=1F_0 = 1,但保证 F0F_0mod998244353\bmod 998244353 下的二次剩余。

边界求一遍二次剩余即可。

多项式幂函数

不保证 F0=1F_0 = 1。可以先找到系数不为00的一项,然后让式子除以这一项最后再乘回来就好了

F(x)k=(F(x)xt)kxtkF(x)^k = \bigg(\frac{F(x)}{x ^ t} \bigg) ^ k x ^ {tk}

分治FFT/NTT

给定序列 ggff,其中

fi=j=1ifijgjf_i = \sum_{j = 1} ^ i f_{i - j}g_j

可以采用如同CDQ分治的方法统计前面的对后面的贡献,时间复杂度 O(nlog2n)O(n \log ^ 2 n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void Divide(int l, int r)
{
if(l == r)return;
int mid = (l + r) >> 1;
Divide(l, mid);
int n = mid - l + 1;
int m = r - l + 1;
int len; init(n, m, len);
for(int i = 0; i <= len; i++)
A[i] = B[i] = 0;
for(int i = l; i <= mid; i++)
A[i - l] = F[i];
for(int i = 1; i <= r - l; i++)
B[i] = G[i];
NTT(A, len, 1); NTT(B, len, 1);
for(int i = 0; i < len; i++)
A[i] = A[i] * B[i] % mod;
NTT(A, len, - 1);
for(int i = mid + 1; i <= r; i++)
F[i] = (F[i] + A[i - l]) % mod;
Divide(mid + 1, r);
}

ff, 这里还有一个多项式求逆的方法

F(x)=i=0fixi,G(x)=i=0gixiF(x) = \sum_{i = 0} ^ {\infty} f_i x ^ i , G(x) = \sum_{i = 0} ^ {\infty}g_i x ^ i,且 g0=0g_0 = 0

所以有

F(x)G(x)=i=0j+k=ifjgk=F(x)f0x0F(x)G(x)F(x)f0(modxn)F(x)(1G(x))1(modxn)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}

多项式除法

给定一个 nn 次多项式 F(x)F(x) 和一个 mm 次多项式 G(x)G(x), 求多项式 A(x),B(x)A(x),B(x),满足:

  • A(x)A(x) 次数为 nmn - mB(x)B(x) 次数小于 mm
  • F(x)=A(x)G(x)+B(x)F(x) = A(x) \ast G(x) + B(x)

所有运算在模 998244353998244353 下进行。
定义一种让多项式反转的操作为 A(x)=xnA(1x)A'(x) = x^n A(\frac{1}{x}),然后化简式子

F(x)=A(x)G(x)+B(x)xnF(1x)=xnmA(1x)xmG(1x)+xnm+1xm1B(1x)F(x)=A(x)G(x)+xnm+1B(x)F(x)A(x)G(x)(modxnm+1)A(x)F(x)G1(x)(modxnm+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}}

先进行多项式求逆,然后再推 B(x)=F(x)A(x)G(x)B(x) = F(x) - A(x) \ast G(x)

多项式多点求值

多项式朴素算法求值,单次是 O(n)O(n) 的,多次 O(n2)O(n^2),对于多点求值时,可以用类似分治的算法优化到 O(nlog2n)O(n \log ^ 2 n)

对于多项式 FF,和一个点 x0x_0,我们可以构造多项式 F(x)=G(x)Q(x)+R(x)F(x) = G(x) \ast Q(x) + R(x), 若 G(x)=xx0G(x) = x - x_0, 就可以得到 R(x0)R(x_0) 即为对应的点值, 可以用分治的思想,进行多项式取模, 分别对 i=lmid(xxi)\prod_{i = l} ^ mid (x - x_i)i=mid+1r(xxi)\prod _ {i = mid + 1} ^ r (x - x_i) 取模后递归计算。

多项式快速插值

对于 nn 个已知的点值,直接使用拉格朗日插值,时间复杂度是 O(n2)O(n ^ 2) 的,使用分治可以将其优化到 O(nlog2n)O(n \log ^ 2 n)

首先对于拉格朗日插值公式,

f(x)=i=0nyiijxxjxixjf(x) = \sum_{i = 0} ^ n y_i \prod_{i \not = j} \frac{x - x_j}{x_i - x_j}

转化成,

f(x)=i=0nyiij(xixj)ij(xxj)f(x) = \sum_{i = 0} ^ n \frac{y_i}{\prod_{i \not = j} (x_i - x_j)} \prod_{i \not = j} (x - x_j)

g(x)=i=1n(xxi)g(x) = \prod_{i = 1} ^ n (x - x_i) 然后相当于求 g(x)xxi\frac{g(x)}{x - x_i},根据洛必达法则,若

limxaf(x)=0,limxag(x)=0\lim_{x \to a} f(x) = 0, \lim_{x \to a} g(x) = 0

limxaf(x)g(x)=limxaf(x)g(x)\lim_{x \to a} \frac{f(x)}{g(x)} = \lim_{x \to a} \frac{f'(x)}{g'(x)}

然后代入后可以得出,所求即为 g(xi)g'(x_i),这样就可以先算出 gg 然后求导进行一次多点求值,然后考虑分治,

fl,rf_{l,r} 表示 llrr 之间的点插值得出的答案,则有

fl,r=i=lryig(xi)j=l,ijr(xxj)=j=mid+1r(xxj)i=lmidyig(xi)j=l,ijmid(xxj)+j=lmid(xxj)i=mid+1ryig(xi)j=mid+1,ijr(xxj)=j=mid+1r(xxj)fl,mid+j=lmid(xxj)fmid+1,r\begin{aligned} f_{l, r} &= \sum_{i = l} ^ r \frac{y_i}{g'(x_i)} \prod_{j = l, i \not = j} ^ r (x - x_j) \\ &= \prod_{j = mid + 1} ^ r (x - x_j) \sum_{i = l} ^ {mid} \frac{y_i}{g'(x_i)} \prod _ {j = l, i \not = j} ^ {mid} (x - x_j) + \prod_{j = l} ^ {mid} (x - x_j) \sum_{i = mid + 1} ^ r \frac{y_i}{g'(x_i)} \prod_{j = mid + 1, i \not = j} ^ r (x - x_j) \\ &= \prod_{j = mid + 1} ^ r (x - x_j) f_{l, mid} + \prod_{j = l} ^ {mid} (x - x_j) f_{mid + 1, r} \end{aligned}

直接分治计算即可。

多项式复合函数

F(x),G(x)F(x), G(x),求

H(x)F(G(x))(modxn+1)H(x) \equiv F(G(x)) \pmod {x^{n + 1}}

即:

H(x)i=0n[xi]F(x)×G(x)i(modxn+1)H(x) \equiv \sum_{i = 0} ^n[x^i] F(x) \times G(x)^i \pmod {x^{n + 1}}

998244353998244353 取模。

m=nm = \sqrt n,则

i=0n[xi]F(x)G(x)i=i=0m1j=0m1[xim+j]F(x)G(x)im+j=i=0m1G(x)imj=0m1[xim+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

然后预处理 G(x)imG(x)^{im}G(x)jG(x)^j,其它的直接暴力计算, 时间复杂度 O(n2+nnlogn)O(n^2 + n \sqrt n \log n)

多项式复合函数逆

F(x)F(x),求

G(F(x))x(modxn)G(F(x)) \equiv x \pmod {x ^ n}

998244353998244353 取模。

有拉格朗日反演公式:

[xn]G(x)=1n[xn1](xF(x))n[x^n]G(x) = \frac{1}{n}[x^{n - 1}](\frac{x}{F(x)})^n

这个是求单项系数的, 需要求的是所有系数。

利用解决多项式复合函数的方法, 令 m=nm = \sqrt n, 则

G(x)=i=1n(1i[xi1](xF(x))i)=i=0m1j=1m(1im+j[xim+j1](xF(x))im+j)xim+j=i=0m1j=1m(1im+j[xim+j1](xF(x))im(xF(x))j)xim+jG(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}

然后就是和多项式复合函数的一样的处理方法,时间复杂度 O(n2+nlogn)O(n^2 + \sqrt n \log n)

下降幂多项式乘法

给定两个下降幂多项式求 FGF \ast G

先构造出点值的EGF,F=i=0F(i)i!xiF' = \sum_{i = 0} ^ {\infty} \frac{F(i)}{i!} x ^ i,

F=i=0xii!j=0ni!(ij)!×FjF=i=0xij=0n1(ij)!×FjF' = \sum_{i = 0} ^ {\infty} \frac{x ^ i}{i!} \sum_{j = 0} ^ n \frac{i!}{(i - j)!} \times F_j \\ F' = \sum_{i = 0} ^ {\infty} x ^ i \sum_{j = 0} ^ n \frac{1}{(i - j)!} \times F_j\\

换一下枚举顺序,

F=i=0nFij=i1(ji)!xjF=i=0nFixij=01j!xjF' = \sum_{i = 0} ^ n F_i \sum_{j = i} ^ {\infty} \frac{1}{(j - i)!} x ^ j \\ F' = \sum_{i = 0} ^ n F_i x ^ i \sum_{j = 0} ^ {\infty} \frac{1}{j!} x ^ j \\

最后可以得出,

F=exi=0nFixiF' = e ^ x \sum_{i = 0} ^ n F_i x ^ i

也就是说直接卷上 i=01i!xi\sum _ {i = 0} ^ {\infty} \frac{1}{i!} x ^ i 即可得到下降幂多项式点值的EGF。

得到点值EGF之后,直接相乘,然后再卷上 ex=i=0(1)ii!xie ^ {-x} = \sum_{i = 0} ^ {\infty} \frac{(-1) ^ i}{i!} x ^ i 就可以得到卷积结果。

普通多项式转下降幂多项式

根据下降幂多项式和其点值EGF的关系,我们可以先对普通多项式进行多点求值,然后求出EGF,在卷上exe ^ {-x} 即可得出。 时间复杂度 O(nlog2n)O(n \log ^ 2 n)

还可以用分治NTT,同样复杂度,但常数更小。

下降幂多项式转普通多项式

先卷上exe ^ x然后就可求出点值的EGF, 然后由于点值是连续的,可以直接阶乘优化插值,时间复杂度O(nlog2n)O(n log ^ 2 n)

同样也有分治NTT的做法。

多项式三角函数

可以使用幂函数解决

cosF(x)=eiF(x)+eiF(x)2sinF(x)=eiF(x)eiF(x)2i\cos F(x) = \frac{e ^ {iF(x)} + e ^ {-iF(x)}}{2} \\ \sin F(x) = \frac{e ^ {iF(x)} - e ^ {-iF(x)}}{2i}

多项式反三角函数

见洛谷P5265

多项式相关

模板

预处理和IO

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

#define ll long long
#define poly vector <ll>
const int N = 4e5 + 10; // size
const int mod = 998244353;
const int g = 3;
const int gi = 332748118;
const int inv2 = (mod + 1) >> 1;

namespace MOD
{
ll add(ll x, ll y){return x + y >= mod ? x + y - mod : x + y;}
ll dec(ll x, ll y){return x - y < 0 ? x - y + mod : x - y;}
}
using namespace MOD;

// 快模

namespace IO
{
inline ll read()
{
ll x = 0, f = 1;char ch = getchar();
while(!isdigit(ch)){if(ch == '-')f = -1;ch = getchar();}
while(isdigit(ch)){x =((x << 3ll) + (x << 1ll)) + (ch ^ 48ll);ch = getchar();}
return x * f;
}
inline void write(ll x)
{
char ch[100];int len = 0;
if(x == 0)ch[++len] = '0';
if(x < 0)putchar('-'), x = -x;
while(x != 0){ch[++len] = x % 10ll + '0'; x /= 10ll;}
while(len)putchar(ch[len--]);
}
}

using namespace IO;

// 快读

inline void read(poly &A, int n)
{
A.resize(n + 1);
for(int i = 0; i <= n; i++)
A[i] = read();
}

inline void print(poly B)
{
for(auto x : B)write(x), putchar(' ');
}

// 读入和输出多项式

inline ll qpow(ll a, ll b)
{
ll t = 1;
while(b != 0)
{
if(b & 1)t = t * a % mod;
a = a * a % mod; b >>= 1;
}
return t;
}

inline ll inv(ll x)
{
return qpow(x, mod - 2);
}

// 快速幂和逆元

ll fv[N];

inline void initfv()
{
fv[1] = 1;
for(ll i = 2; i <= (N - 10) / 4; i++)
fv[i] = fv[mod % i] * (mod - mod / i) % mod;
}

//预处理逆元, 积分用

ll p[3][50];

inline void prework()
{
for(int k = 1, i = 1; k < N; k <<= 1, i++)
{
p[0][i] = qpow(g, (mod - 1) / (k << 1));
p[1][i] = qpow(gi, (mod - 1) / (k << 1));
p[2][i] = inv(k << 1);
}
}

// 预处理快速幂

多项式乘法

主要使用NTT

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
inline 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++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1));
}

// 预处理rev数组

inline void NTT(poly &a, int len, int type)
{
for(int i = 0; i < len; i++)
if(i < rev[i])swap(a[i], a[rev[i]]);
for(int k = 1, o = 1; k < len; k <<= 1, o++)
{
ll x = type == 1 ? p[0][o] : p[1][o];
for(int i = 0; i < len; i += k << 1)
{
ll w = 1;
for(int j = 0; j < k; j++)
{
ll y = a[i + j];
ll z = w * a[i + j + k] % mod;
a[i + j] = add(y, z);
a[i + j + k] = dec(y, z);
w = w * x % mod;
}
}
}
if(type == -1)
{
ll iv = p[2][(int)log2(len)];
for(int i = 0; i < len; i++)
a[i] = a[i] * iv % mod;
}
}

//NTT

多项式基本运算

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
inline poly operator + (poly A, poly B)
{
int n = max(A.size(), B.size());
A.resize(n); B.resize(n);
for(int i = 0; i < n; i++)
A[i] = add(A[i], B[i]);
return A;
}
inline poly operator - (poly A, poly B)
{
int n = max(A.size(), B.size());
A.resize(n); B.resize(n);
for(int i = 0; i < n; i++)
A[i] = dec(A[i], B[i]);
return A;
}
inline poly operator * (poly A, poly B)
{
int len, n = A.size() - 1, m = B.size() - 1;
init(n, m, len);
A.resize(len); B.resize(len);
NTT(A, len, 1); NTT(B, len, 1);
for(int i = 0; i < len; i++)
A[i] = A[i] * B[i] % mod;
NTT(A, len, -1); A.resize(n + m + 1);
return A;
}

分治NTT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
inline void Divide(int l, int r, poly &F, poly &G)
{
if(l == r)return;
int mid = (l + r) >> 1;
Divide(l, mid, F, G);
int n = mid - l + 1;
int m = r - l + 1;
poly A; A.resize(n);
poly B; B.resize(m);
for(int i = l; i <= mid; i++)
A[i - l] = F[i];
for(int i = 1; i <= r - l; i++)
B[i] = G[i];
A = A * B;
for(int i = mid + 1; i <= r; i++)
F[i] = add(F[i], A[i - l]);
Divide(mid + 1, r, F, G);
}

多项式求逆

1
2
3
4
5
6
7
8
9
10
11
12
inline void Inverse(poly &A, poly &B, int n)
{
if(n == 1)return void(B[0] = inv(A[0]));
Inverse(A, B, (n + 1) >> 1);
int len; init(n, n, len);
poly C; C.resize(len); B.resize(len);
for(int i = 0; i < n; i++)C[i] = A[i];
NTT(C, len, 1); NTT(B, len, 1);
for(int i = 0; i < len; i++)
B[i] = dec(2, B[i] * C[i] % mod) * B[i] % mod;
NTT(B, len, -1); B.resize(n);
}

多项式求导

1
2
3
4
5
6
7
inline void Diff(poly &A, poly &B)
{
int n = A.size() - 1;
B.resize(n);
for(ll i = 1; i <= n; i++)
B[i - 1] = i * A[i] % mod;
}

多项式积分

1
2
3
4
5
6
7
8
inline void Integ(poly &A, poly &B)
{
int n = A.size();
B.resize(n + 1);
for(int i = 1; i <= n; i++)
B[i] = A[i - 1] * fv[i] % mod;
B[0] = 0;
}

多项式对数函数

1
2
3
4
5
6
7
8
inline void Ln(poly &A, poly &B)
{
int n = A.size() - 1;
poly F; poly G; G.resize(1);
Diff(A, F); Inverse(A, G, n + 1);
F = F * G;
Integ(F, B); B.resize(n + 1);
}

多项式指数函数

1
2
3
4
5
6
7
8
inline void Exp(poly &A, poly &B, int n)
{
if(n == 1)return void(B[0] = 1);
Exp(A, B, (n + 1) >> 1);
poly G; B.resize(n);
Ln(B, G); G[0] -= 1; B = (A - G) * B;
B.resize(n);
}

多项式开根

1
2
3
4
5
6
7
8
9
10
11
12
13
inline void Sqrt(poly &A, poly &B, int n)
{
if(n == 1)return void(B[0] = 1);
Sqrt(A, B, (n + 1) >> 1);
int len; init(n, n, len); B.resize(n);
poly G, H; H.resize(1); Inverse(B, H, n);
G.resize(len); H.resize(len); B.resize(len);
for(int i = 0; i < n; i++)G[i] = A[i];
NTT(H, len, 1); NTT(B, len, 1); NTT(G, len, 1);
for(int i = 0; i < len; i++)
B[i] = add(B[i], G[i] * H[i] % mod) * inv2 % mod;
NTT(B, len, -1); B.resize(n);
}

多项式幂函数

1
2
3
4
5
6
7
8
inline void Pow(poly &A, poly &B, ll k)
{
int n = A.size() - 1;
poly C; Ln(A, C);
for(int i = 0; i <= n; i++)
C[i] = C[i] * k % mod;
Exp(C, B, n + 1);
}

多项式带余除法

1
2
3
4
5
6
7
8
9
10
11
12
inline void Mod(poly &F, poly &G, poly &A, poly &B)
{
poly f = F, g = G, h;
int n = F.size() - 1, m = G.size() - 1;
reverse(f.begin(), f.end());
reverse(g.begin(), g.end());
g.resize(n - m + 1);
h.resize(1); Inverse(g, h, n - m + 1);
A = f * h; A.resize(n - m + 1);
reverse(A.begin(), A.end());
B = F - (A * G); B.resize(m);
}

多项式多点求值

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
inline void getV(int l, int r, int p)
{
if(l == r)
{
v[p].resize(2);
v[p][0] = mod - a[l];
v[p][1] = 1;
return;
}
int mid = (l + r) >> 1;
getV(l, mid, p << 1);
getV(mid + 1, r, p << 1 | 1);
v[p] = v[p << 1] * v[p << 1 | 1];
}

inline void solve(int l, int r, int p, poly &f)
{
if(l == r)return write(f[0]), putchar('\n'), void();
if(r - l + 1 <= 1000)
{
for(int i = l; i <= r; i++)
{
ll x = 1, sum = 0;
for(int j = 0; j < f.size(); j++)
{
sum = (sum + x * f[j] % mod) % mod;
x = x * a[i] % mod;
}
write(sum); putchar('\n');
}
return;
}
int mid = (l + r) >> 1;poly L, R, A;
Mod(f, v[p << 1], A, L);
solve(l, mid, p << 1, L);
Mod(f, v[p << 1 | 1], A, R);
solve(mid + 1, r, p << 1 | 1, R);
}

多项式快速插值

1
2
3
4
5
6
7
8
9
10
11
12
13
inline void Larange(int l, int r, int p, poly &f)
{
if(l == r)
{
f.resize(1);
f[0] = Y[l] * inv(val[l]) % mod;
return;
}
int mid = (l + r) >> 1; poly L, R;
Larange(l, mid, p << 1, L);
Larange(mid + 1, r, p << 1 | 1, R);
f = (L * v[p << 1 | 1]) + (R * v[p << 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
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
namespace Poly
{
#define ll long long
#define poly vector <ll>
const int N = 4e5 + 10;
const int mod = 998244353;
const int g = 3;
const int gi = 332748118;
const int inv2 = (mod + 1) >> 1;

namespace MOD
{
ll add(ll x, ll y){return x + y >= mod ? x + y - mod : x + y;}
ll dec(ll x, ll y){return x - y < 0 ? x - y + mod : x - y;}
}
using namespace MOD;

namespace IO
{
inline ll read()
{
ll x = 0, f = 1;char ch = getchar();
while(!isdigit(ch)){if(ch == '-')f = -1;ch = getchar();}
while(isdigit(ch)){x =((x << 3ll) + (x << 1ll)) + (ch ^ 48ll);ch = getchar();}
return x * f;
}
inline void write(ll x)
{
char ch[100];int len = 0;
if(x == 0)ch[++len] = '0';
if(x < 0)putchar('-'), x = -x;
while(x != 0){ch[++len] = x % 10ll + '0'; x /= 10ll;}
while(len)putchar(ch[len--]);
}
}

using namespace IO;

int rev[N];

ll p[3][50], fv[N];

inline void read(poly &A, int n)
{
A.resize(n + 1);
for(int i = 0; i <= n; i++)
A[i] = read();
}

inline void print(poly B)
{
for(auto x : B)write(x), putchar(' ');
}

inline ll qpow(ll a, ll b)
{
ll t = 1;
while(b != 0)
{
if(b & 1)t = t * a % mod;
a = a * a % mod; b >>= 1;
}
return t;
}

inline ll inv(ll x)
{
return qpow(x, mod - 2);
}

inline void initfv()
{
fv[1] = 1;
for(ll i = 2; i <=(N - 10) / 4; i++)
fv[i] = fv[mod % i] * (mod - mod / i) % mod;
}

inline void prework()
{
for(int k = 1, i = 1; k < N; k <<= 1, i++)
{
p[0][i] = qpow(g, (mod - 1) / (k << 1));
p[1][i] = qpow(gi, (mod - 1) / (k << 1));
p[2][i] = inv(k << 1);
}
}

inline 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++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1));
}

inline void NTT(poly &a, int len, int type)
{
for(int i = 0; i < len; i++)
if(i < rev[i])swap(a[i], a[rev[i]]);
for(int k = 1, o = 1; k < len; k <<= 1, o++)
{
ll x = type == 1 ? p[0][o] : p[1][o];
for(int i = 0; i < len; i += k << 1)
{
ll w = 1;
for(int j = 0; j < k; j++)
{
ll y = a[i + j];
ll z = w * a[i + j + k] % mod;
a[i + j] = add(y, z);
a[i + j + k] = dec(y, z);
w = w * x % mod;
}
}
}
if(type == -1)
{
ll iv = p[2][(int)log2(len)];
for(int i = 0; i < len; i++)
a[i] = a[i] * iv % mod;
}
}

inline poly operator + (poly A, poly B)
{
int n = max(A.size(), B.size());
A.resize(n); B.resize(n);
for(int i = 0; i < n; i++)
A[i] = add(A[i], B[i]);
return A;
}
inline poly operator - (poly A, poly B)
{
int n = max(A.size(), B.size());
A.resize(n); B.resize(n);
for(int i = 0; i < n; i++)
A[i] = dec(A[i], B[i]);
return A;
}
inline poly operator * (poly A, poly B)
{
int len, n = A.size() - 1, m = B.size() - 1;
init(n, m, len);
A.resize(len); B.resize(len);
NTT(A, len, 1); NTT(B, len, 1);
for(int i = 0; i < len; i++)
A[i] = A[i] * B[i] % mod;
NTT(A, len, -1); A.resize(n + m + 1);
return A;
}

inline void Divide(int l, int r, poly &F, poly &G)
{
if(l == r)return;
int mid = (l + r) >> 1;
Divide(l, mid, F, G);
int n = mid - l + 1;
int m = r - l + 1;
poly A; A.resize(n);
poly B; B.resize(m);
for(int i = l; i <= mid; i++)
A[i - l] = F[i];
for(int i = 1; i <= r - l; i++)
B[i] = G[i];
A = A * B;
for(int i = mid + 1; i <= r; i++)
F[i] = add(F[i], A[i - l]);
Divide(mid + 1, r, F, G);
}

inline void Inverse(poly &A, poly &B, int n)
{
if(n == 1)return void(B[0] = inv(A[0]));
Inverse(A, B, (n + 1) >> 1);
int len; init(n, n, len);
poly C; C.resize(len); B.resize(len);
for(int i = 0; i < n; i++)C[i] = A[i];
NTT(C, len, 1); NTT(B, len, 1);
for(int i = 0; i < len; i++)
B[i] = dec(2, B[i] * C[i] % mod) * B[i] % mod;
NTT(B, len, -1); B.resize(n);
}

inline void Diff(poly &A, poly &B)
{
int n = A.size() - 1;
B.resize(n);
for(ll i = 1; i <= n; i++)
B[i - 1] = i * A[i] % mod;
}

inline void Integ(poly &A, poly &B)
{
int n = A.size();
B.resize(n + 1);
for(int i = 1; i <= n; i++)
B[i] = A[i - 1] * fv[i] % mod;
B[0] = 0;
}

inline void Ln(poly &A, poly &B)
{
int n = A.size() - 1;
poly F; poly G; G.resize(1);
Diff(A, F); Inverse(A, G, n + 1);
F = F * G;
Integ(F, B); B.resize(n + 1);
}

inline void Exp(poly &A, poly &B, int n)
{
if(n == 1)return void(B[0] = 1);
Exp(A, B, (n + 1) >> 1);
poly G; B.resize(n);
Ln(B, G); G[0] -= 1; B = (A - G) * B;
B.resize(n);
}

inline void Sqrt(poly &A, poly &B, int n)
{
if(n == 1)return void(B[0] = 1);
Sqrt(A, B, (n + 1) >> 1);
int len; init(n, n, len); B.resize(n);
poly G, H; H.resize(1); Inverse(B, H, n);
G.resize(len); H.resize(len); B.resize(len);
for(int i = 0; i < n; i++)G[i] = A[i];
NTT(H, len, 1); NTT(B, len, 1); NTT(G, len, 1);
for(int i = 0; i < len; i++)
B[i] = add(B[i], G[i] * H[i] % mod) * inv2 % mod;
NTT(B, len, -1); B.resize(n);
}

inline void Pow(poly &A, poly &B, ll k)
{
int n = A.size() - 1;
poly C; Ln(A, C);
for(int i = 0; i <= n; i++)
C[i] = C[i] * k % mod;
Exp(C, B, n + 1);
}

inline void Mod(poly &F, poly &G, poly &A, poly &B)
{
poly f = F, g = G, h;
int n = F.size() - 1, m = G.size() - 1;
if(n < m)return void(B = F);
reverse(f.begin(), f.end());
reverse(g.begin(), g.end());
g.resize(n - m + 1);
h.resize(1); Inverse(g, h, n - m + 1);
A = f * h; A.resize(n - m + 1);
reverse(A.begin(), A.end());
B = F - (A * G); B.resize(m);
}

inline poly operator % (poly A, poly B)
{
poly F, G;
Mod(A, B, F, G);
return G;
}
}

using namespace Poly;

多项式模板纯享版

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
namespace Poly
{
#define ll long long
#define poly vector <ll>
const int N = 4e5 + 10;const int mod = 998244353;const int g = 3;const int gi = 332748118;const int inv2 = (mod + 1) >> 1;
namespace MOD{ll add(ll x, ll y){return x + y >= mod ? x + y - mod : x + y;}ll dec(ll x, ll y){return x - y < 0 ? x - y + mod : x - y;}}using namespace MOD;
namespace IO{inline ll read(){ll x = 0, f = 1;char ch = getchar();while(!isdigit(ch)){if(ch == '-')f = -1;ch = getchar();}while(isdigit(ch)){x =((x << 3ll) + (x << 1ll)) + (ch ^ 48ll);ch = getchar();}return x * f;}inline void write(ll x){char ch[100];int len = 0;if(x == 0)ch[++len] = '0';if(x < 0)putchar('-'), x = -x;while(x != 0){ch[++len] = x % 10ll + '0'; x /= 10ll;}while(len)putchar(ch[len--]);}}using namespace IO;int rev[N];ll p[3][50], fv[N];
inline void read(poly &A, int n){A.resize(n + 1);for(int i = 0; i <= n; i++)A[i] = read();}
inline void print(poly B){for(auto x : B)write(x), putchar(' ');}
inline ll qpow(ll a, ll b){ll t = 1;while(b != 0){if(b & 1)t = t * a % mod;a = a * a % mod; b >>= 1;}return t;}
inline ll inv(ll x){return qpow(x, mod - 2);}
inline void initfv(){fv[1] = 1;for(ll i = 2; i <=(N - 10) / 4; i++)fv[i] = fv[mod % i] * (mod - mod / i) % mod; }
inline void prework(){for(int k = 1, i = 1; k < N; k <<= 1, i++){p[0][i] = qpow(g, (mod - 1) / (k << 1));p[1][i] = qpow(gi, (mod - 1) / (k << 1));p[2][i] = inv(k << 1);}}
inline 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++)rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (cnt - 1));}
inline void NTT(poly &a, int len, int type){for(int i = 0; i < len; i++)if(i < rev[i])swap(a[i], a[rev[i]]);for(int k = 1, o = 1; k < len; k <<= 1, o++){ll x = type == 1 ? p[0][o] : p[1][o];for(int i = 0; i < len; i += k << 1){ll w = 1;for(int j = 0; j < k; j++){ll y = a[i + j];ll z = w * a[i + j + k] % mod;a[i + j] = add(y, z);a[i + j + k] = dec(y, z);w = w * x % mod;}}}if(type == -1){ll iv = p[2][(int)log2(len)];for(int i = 0; i < len; i++)a[i] = a[i] * iv % mod;}}
inline poly operator + (poly A, poly B){int n = max(A.size(), B.size());A.resize(n); B.resize(n);for(int i = 0; i < n; i++)A[i] = add(A[i], B[i]);return A;}
inline poly operator - (poly A, poly B){int n = max(A.size(), B.size());A.resize(n); B.resize(n);for(int i = 0; i < n; i++)A[i] = dec(A[i], B[i]);return A;}
inline poly operator * (poly A, poly B){int len, n = A.size() - 1, m = B.size() - 1;init(n, m, len);A.resize(len); B.resize(len);NTT(A, len, 1); NTT(B, len, 1);for(int i = 0; i < len; i++)A[i] = A[i] * B[i] % mod;NTT(A, len, -1); A.resize(n + m + 1);return A;}
inline void Divide(int l, int r, poly &F, poly &G){if(l == r)return;int mid = (l + r) >> 1;Divide(l, mid, F, G);int n = mid - l + 1;int m = r - l + 1;poly A; A.resize(n);poly B; B.resize(m);for(int i = l; i <= mid; i++)A[i - l] = F[i];for(int i = 1; i <= r - l; i++)B[i] = G[i];A = A * B;for(int i = mid + 1; i <= r; i++)F[i] = add(F[i], A[i - l]);Divide(mid + 1, r, F, G);}
inline void Inverse(poly &A, poly &B, int n){if(n == 1)return void(B[0] = inv(A[0]));Inverse(A, B, (n + 1) >> 1);int len; init(n, n, len);poly C; C.resize(len); B.resize(len);for(int i = 0; i < n; i++)C[i] = A[i];NTT(C, len, 1); NTT(B, len, 1);for(int i = 0; i < len; i++)B[i] = dec(2, B[i] * C[i] % mod) * B[i] % mod;NTT(B, len, -1); B.resize(n);}
inline void Diff(poly &A, poly &B){int n = A.size() - 1;B.resize(n);for(ll i = 1; i <= n; i++)B[i - 1] = i * A[i] % mod;}
inline void Integ(poly &A, poly &B){int n = A.size();B.resize(n + 1);for(int i = 1; i <= n; i++)B[i] = A[i - 1] * fv[i] % mod;B[0] = 0;}
inline void Ln(poly &A, poly &B){int n = A.size() - 1;poly F; poly G; G.resize(1);Diff(A, F); Inverse(A, G, n + 1);F = F * G;Integ(F, B); B.resize(n + 1);}
inline void Exp(poly &A, poly &B, int n){if(n == 1)return void(B[0] = 1);Exp(A, B, (n + 1) >> 1); poly G; B.resize(n);Ln(B, G); G[0] -= 1; B = (A - G) * B;B.resize(n);}
inline void Sqrt(poly &A, poly &B, int n){if(n == 1)return void(B[0] = 1);Sqrt(A, B, (n + 1) >> 1);int len; init(n, n, len); B.resize(n);poly G, H; H.resize(1); Inverse(B, H, n);G.resize(len); H.resize(len); B.resize(len);for(int i = 0; i < n; i++)G[i] = A[i];NTT(H, len, 1); NTT(B, len, 1); NTT(G, len, 1);for(int i = 0; i < len; i++)B[i] = add(B[i], G[i] * H[i] % mod) * inv2 % mod;NTT(B, len, -1); B.resize(n);}
inline void Pow(poly &A, poly &B, ll k){int n = A.size() - 1;poly C; Ln(A, C);for(int i = 0; i <= n; i++)C[i] = C[i] * k % mod;Exp(C, B, n + 1);}
inline void Mod(poly &F, poly &G, poly &A, poly &B){poly f = F, g = G, h;int n = F.size() - 1, m = G.size() - 1;if(n < m)return void(B = F); reverse(f.begin(), f.end());reverse(g.begin(), g.end());g.resize(n - m + 1);h.resize(1); Inverse(g, h, n - m + 1);A = f * h; A.resize(n - m + 1);reverse(A.begin(), A.end());B = F - (A * G); B.resize(m);}
inline poly operator % (poly A, poly B){poly F, G;Mod(A, B, F, G);return G;}
}
using namespace Poly;

生成函数

普通生成函数(OGF)

定义序列 aa 的生成函数为

F(x)=i=0aixiF(x) = \sum_{i = 0} ^ {\infty} a_ix^i

运算

其加法运算为

F(x)±G(x)=i=0(ai±bi)xiF(x) \pm G(x) = \sum_{i = 0} ^ {\infty} (a_i \pm b_i) x^ i

乘法运算,也就是卷积为

F(x)G(x)=i=0(j=0iajbij)xiF(x) \ast G(x) = \sum_{i = 0} ^ {\infty} \bigg( \sum_{j = 0} ^ i a_jb_{i - j} \bigg)x^i

其为序列 i=0naibni\sum_ {i = 0}^n a_i b_{n - i} 的生成函数。

封闭形式

根据等比数列求和公式可得

F(x)=i=0aixi=a0×1x1x=a01xF(x) = \sum_{i = 0} ^ {\infty} a_i x^i = a_0 \times \frac{1 - x ^ {\infty}}{1 - x} = \frac{a_0}{1 - x}

牛顿二项式定理

定义组合数为

(nm)=nmm!,nC,mN\dbinom{n}{m} = \frac{n ^ {\underline{m}}}{m!}, n \in \mathbb{C}, m \in \mathbb{N}

指数型生成函数(EGF)

定义序列 aa 的指数型生成函数为

F(x)=i=0aixii!F(x) = \sum_{i = 0} ^ {\infty} a_i \frac{x^i}{i !}

对于两个序列 aa 和 序列 bb, 设它们的指数生成函数分别为 F(x)F(x)G(x)G(x), 有

FG=i=0aixii!j=0bjxjj!=i=0xij=0iajbij1j!(ij)!=i=0xii!j=0i(ij)ajbij\begin{aligned} F \ast G &= \sum_{i = 0} ^ {\infty} a_i \frac{x ^ i}{i!} \sum_{j = 0} ^ {\infty} b_j \frac{x ^ j}{j!} \\ &= \sum_{i = 0} ^ {\infty} x ^ i \sum_{j = 0} ^ i a_jb_{i - j} \frac{1}{j!(i - j)!} \\ &= \sum_{i = 0} ^ {\infty} \frac{x ^ i}{i!} \sum_{j = 0} ^ i \dbinom{i}{j} a_j b_{i - j} \end{aligned}

两个函数的卷积对应的是序列 i=0n(ni)aibni\sum_{i = 0} ^ n \dbinom{n}{i} a_i b_{n - i} 的指数型生成函数。

显然有

ex=i=1e0xii!=i=1xii!ex+ex2=i=0x2i(2i)!exex2=i=0x2i+1(2i+1)!e^x = \sum_{i = 1}^{\infty} \frac{e^0 x^i}{i!} = \sum_{i = 1}^{\infty}\frac{x^i}{i!} \\ \frac{e^x + e^{-x}}{2} = \sum_{i = 0}^{\infty} \frac{x^{2i}}{(2i)!} \\ \frac{e^x - e ^ {-x}}{2} =\sum_{i = 0}^{\infty} \frac{x ^ {2i + 1}}{(2i+1)!}

可以应用到组合数学中。

作者

Jekyll_Y

发布于

2023-01-12

更新于

2023-03-02

许可协议

评论