【更新中】浅谈模意义下形式幂级数的几种操作

前言

近几年信息学竞赛中出现了一类利用 生成函数 进行计数的题目,这类题目往往需要一顿推导之后得出一个或几个容易计算的生成函数与代表答案的生成函数间的关系。

由于这类题目往往需要将结果对一个大质数取模,因此我们可以用模意义下形式幂级数的一些操作来进行这些生成函数之间的运算。

本文将从简单的离散傅里叶变换开始,逐步带你了解这一类基于离散卷积的形式幂级数操作。


你需要知道的几点

  • 多项式
    • 由数或字母或两者的乘积组成的代数式叫单项式(单个数或字母也为单项式)
      • 单项式中的常数因子为单项式的系数
      • 单项式中所有字母的指数之和为单项式的次数
    • 由若干个单项式相加组成的代数式叫多项式
      • 多项式各项中次数最大的单项式的次数为多项式的次数
  • 形式幂级数
    • 定义域为 Z+\mathbb Z^+ 或其有限子集的函数叫数列(也就是一些有序的数)
    • 将数列的每一项求和得到的函数叫级数,一般所讲的级数大多指无穷级数,即数列有无穷项
    • 形如 i=0ai(xx0)i\sum_{i=0}^{\infty}{a_i(x-x_0)^i} 的级数叫幂级数x0x_0 为常数)
      • 其中 aia_i 为幂级数的系数(可以类比多项式的各项系数)
      • 若存在一个非负实数 rr 使得幂级数在 xx0<r|x-x_0|<r 时趋近于一个确定的值(收敛),在 xx0>r|x-x_0|>r 时不趋于任何值(发散),则称 rr 为幂级数的收敛半径,否则称该幂级数的收敛半径为 ++\infty
    • 将一个数列的各项作为幂级数的系数,所得到的幂级数不一定是收敛的(收敛半径可能为 00 )。但我们淡化其收敛与发散的性质,认为它是收敛的,则可以得到一个形式上类似幂级数的东西(也就是长得像幂级数),我们将其称为形式幂级数
      • 若一个形式幂级数从某一项开始,后面项的系数全部为 00 ,则我们称其为形式多项式
  • 离散卷积
    • f(x)f(x)g(x)g(x) 为定义在 R\mathbb R 上的两个可积函数,则称 f(τ)g(xτ)dτ\int_{-\infty}^{\infty}{f(\tau)g(x-\tau)d\tau} 关于 xx 的函数为 f(x)f(x)g(x)g(x)卷积
    • 与卷积类似,我们称数列 cn=i=aibnic_n=\sum_{i=-\infty}^{\infty}{a_ib_{n-i}} 为数列 ana_n 与数列 bnb_n离散卷积
    • 形式幂级数与幂级数一样可以进行运算,其加减运算即是将对应位的系数相加减,而其乘法运算结果定义为两个形式幂级数系数的离散卷积构成的形式幂级数
    • 形式幂级数存在乘法逆元当且仅当 a00a_0\ne0 ,且若存在,则逆元唯一

后面的形式幂级数运算若无特殊说明,全部在 modxn+1\bmod{x^{n+1}} 意义下进行,也即为形式多项式


记号与约定

  1. 我们记以数列 {fn}\{f_n\} 为系数的形式幂级数为 F(x)F(x)
  2. 我们记 [xn]F(x)[x^n]F(x)F(x)F(x)nn 次项系数。
  3. 我们记 figif_ig_ifi×gif_i\times g_i ,而 F(x)G(x)F(x)G(x)F(x)F(x)G(x)G(x) 的离散卷积。

多项式乘法

快速傅里叶变换

我们知道,两个形式幂级数做乘法即为它们系数的离散卷积。

假设我们已知 F(x)F(x)G(x)G(x) 的系数,我们要求得 H(x)=F(x)G(x)H(x)=F(x)G(x)

根据定义:

hn=i=0nfigniH(x)=i=0j=0ifjgijxih_n=\sum_{i=0}^{n}{f_ig_{n-i}}\Rightarrow H(x)=\sum_{i=0}^{\infty}{\sum_{j=0}^{i}{f_jg_{i-j}x^i}}

很显然,我们求出 H(x)H(x) 一项的时间复杂度是 Θ(n)\Theta(n) 的,求出 nn 项的时间复杂度是 Θ(n2)\Theta(n^2) 的,这肯定是不能接受的。我们来考虑怎样更快速地求得 H(x)H(x)

上面的运算是基于用系数来表示多项式的,事实上,由于 n+1n+1 个点可以唯一确定一个 nn 次多项式,我们可以改用 n+1n+1 个点来描述我们的多项式。并且由于我们并不关心形式幂级数的 xx 的取值,我们可以任意选取实数甚至虚数作为 xx 的值。

这样做的好处是什么呢?我们发现,两个点值表达式相乘只需要将对应点的值相乘即可,也就是说,两个点值表达式做乘法运算是 Θ(n)\Theta(n) 的!

如果我们可以快速地将系数表达式转化成点值表达式,并快速地将点值表达式转化回来,就可以快速地完成多项式乘法。那么具体如何来做呢?

我们先介绍一类神奇的数——单位根 ω\omega

单位根

nn 次单位根是 nn 次幂为 11 的复数。

也即, nn 次单位根 ωn\omega_n 满足 ωnn=1\omega_n^n=1

由欧拉公式有 ωnk=cos(k×2πn)+isin(k×2πn)\omega_n^k=\cos(k\times\frac{2\pi}{n})+i\sin(k\times\frac{2\pi}{n})

在后文中,我们假设 nn22 的正整数次幂:

于是单位根有这几个性质

  1. ωnk+n2=ωnk\omega_n^{k+\frac n2}=-\omega_n^k
  2. ω2n2k=ωnk\omega_{2n}^{2k}=\omega_n^k
  3. ωn0=ωnn=1\omega_n^0=\omega_n^n=1

我们来简单证明一下这几个性质:

  1. ωnk+n2=ωnk\omega_n^{k+\frac n2}=-\omega_n^k

ωnk+n2=cos((k+n2)2πn)+isin((k+n2)2πn)=cos(k×2πn+π)+isin(k×2πn+π)=(cos(k×2πn)+isin(k×2πn))=ωnk\begin{aligned} \omega_n^{k+\frac n2}&=\cos((k+\frac n2)\frac{2\pi}{n})+i\sin((k+\frac n2)\frac{2\pi}{n}) \\ &=\cos(k\times\frac{2\pi}{n}+\pi)+i\sin(k\times\frac{2\pi}{n}+\pi) \\ &=-(\cos(k\times\frac{2\pi}{n})+i\sin(k\times\frac{2\pi}{n})) \\ &=-\omega_n^k \end{aligned}

  1. ω2n2k=ωnk\omega_{2n}^{2k}=\omega_n^k

ω2n2k=cos(2k×2π2n)+isin(2k×2π2n)=cos(k×2πn)+isin(k×2πn)=ωnk\begin{aligned} \omega_{2n}^{2k}&=\cos(2k\times\frac{2\pi}{2n})+i\sin(2k\times\frac{2\pi}{2n}) \\ &=\cos(k\times\frac{2\pi}{n})+i\sin(k\times\frac{2\pi}{n}) \\ &=\omega_n^k \end{aligned}

有了这两个性质,我们就能使用膜法了!

快速傅里叶变换

我们考虑将 ωn\omega_n0n10\sim n-1 次幂作为 xx 代入多项式 F(x)F(x) 并求出点值。

也即求出原多项式的离散傅里叶变换(DFT)

我们先将 F(x)F(x) 的各项按次数奇偶性分类:

F(x)=i=0n1fixi=i=0n21f2ix2i+i=0n21f2i+1x2i+1\begin{aligned} F(x)&=\sum_{i=0}^{n-1}{f_ix^i} \\ &=\sum_{i=0}^{\frac n2-1}{f_{2i}x^{2i}}+\sum_{i=0}^{\frac n2-1}{f_{2i+1}x^{2i+1}} \\ \end{aligned}

F1(x)=i=0n21f2ix2iF_1(x)=\sum_{i=0}^{\frac n2-1}{f_{2i}x^{2i}}F2(x)=i=0n21f2i+1x2i+1F_2(x)=\sum_{i=0}^{\frac n2-1}{f_{2i+1}x^{2i+1}} ,则有 F(x)=F1(x2)+xF2(x2)F(x)=F_1(x^2)+xF_2(x^2)

ωnk\omega_n^k 代入 xx

F(ωnk)=F1(ωn2k)+ωnkF2(ωn2k)F(\omega_n^k)=F_1(\omega_n^{2k})+\omega_n^kF_2(\omega_n^{2k})

再将 ωnk+n2\omega_n^{k+\frac n2} 代入 xx

F(ωnk+n2)=F1(ωn2k+n)+ωnk+n2F2(ωn2k+n)=F1(ωn2k×ωnn)+(ωnk×ω2nn)F2(ωn2k×ωnn)=F1(ωn2k)ωnkF2(ωn2k)\begin{aligned} F(\omega_n^{k+\frac n2})&=F_1(\omega_n^{2k+n})+\omega_n^{k+\frac n2}F_2(\omega_n^{2k+n}) \\ &=F_1(\omega_n^{2k}\times\omega_n^n)+(\omega_n^k\times\omega_{2n}^n)F_2(\omega_n^{2k}\times\omega_n^n) \\ &=F_1(\omega_n^{2k})-\omega_n^kF_2(\omega_n^{2k}) \end{aligned}

发现了吗?这是膜法!

这两个式子间只有第二部分的符号有区别,也就是说,我们计算第一个式子的同时可以直接得到第二个式子的值!

将这种奇偶分类求一半的操作递归下去,我们就得到了一个可以在 Θ(nlogn)\Theta(n\log n) 的时间复杂度内将多项式从系数表达式转化为以单位根各次幂为自变量的点值表达式的算法。

快速傅里叶逆变换

接下来我们再来考虑将这样的点值表达式转化回系数表达式的方法。

也即离散傅里叶逆变换(IDFT)

我们令 {y0,y1,y2,,yn1}\{y_0,y_1,y_2,\cdots,y_{n-1}\}{f0,f1,f2,,fn1}\{f_0,f_1,f_2,\cdots,f_{n-1}\} 的离散傅里叶变换

假设存在 {c0,c1,c1,,cn1}\{c_0,c_1,c_1,\cdots,c_{n-1}\} 满足 ck=i=0n1yi(ωnk)ic_k=\sum_{i=0}^{n-1}{y_i(\omega_n^{-k})^i}

也即 Y(x)Y(x)ωnk\omega_n^{-k} 处的点值,则有:

ck=i=0n1yi(ωnk)i=i=0n1(j=0n1fj(ωni)j)(ωnk)i=i=0n1j=0n1fj(ωnj)i(ωnk)i=i=0n1j=0n1fj(ωnjk)i=j=0n1fji=0n1(ωnjk)i\begin{aligned} c_k&=\sum_{i=0}^{n-1}{y_i(\omega_n^{-k})^i} \\ &=\sum_{i=0}^{n-1}{(\sum_{j=0}^{n-1}{f_j(\omega_n^i)^j})(\omega_n^{-k})^i} \\ &=\sum_{i=0}^{n-1}{\sum_{j=0}^{n-1}{f_j(\omega_n^j)^i}(\omega_n^{-k})^i} \\ &=\sum_{i=0}^{n-1}{\sum_{j=0}^{n-1}{f_j(\omega_{n}^{j-k})^i}} \\ &=\sum_{j=0}^{n-1}{f_j\sum_{i=0}^{n-1}{(\omega_n^{j-k})^i}} \end{aligned}

S(x)=i=0n1xiS(x)=\sum_{i=0}^{n-1}{x^i} ,将 ωnk(k0)\omega_n^k(k\ne0) 代入:

{S(ωnk)=i=0n1(ωnk)iωnkS(ωnk)=i=1n(ωnk)iωnkS(ωnk)S(ωnk)=(ωnk)n1S(ωnk)=(ωnk)n1ωnk1=0\because \begin{cases}\begin{aligned} S(\omega_n^k)&=\sum_{i=0}^{n-1}{(\omega_n^k)^i} \\ \omega_n^kS(\omega_n^k)&=\sum_{i=1}^{n}{(\omega_n^k)^i} \\ \end{aligned}\end{cases} \Rightarrow\omega_n^kS(\omega_n^k)-S(\omega_n^k)=(\omega_n^k)^n-1 \\ \therefore S(\omega_n^k)=\frac{(\omega_n^k)^n-1}{\omega_n^k-1}=0

而当 k=0k=0 时显然 S(ωn0)=nS(\omega_n^0)=n

接着来看之前的式子:

ck=j=0n1fji=0n1(ωnjk)ic_k=\sum_{j=0}^{n-1}{f_j\sum_{i=0}^{n-1}{(\omega_n^{j-k})^i}}

式子里面的 Σ\Sigmajkj\ne k 时值为 00 ,而 j=kj=k 时值为 nn 。因此有 ck=nfkfk=cknc_k=nf_k\Rightarrow f_k=\frac{c_k}{n}

于是只要对原多项式的离散傅里叶变换,以 ωnk\omega_n^{-k} 为单位根做一次快速傅里叶变换,再将结果除以 nn 即可转化回原多项式。

代码实现

根据上面的理论可以很容易地写出快速傅里叶变换(FFT)的递归实现,但是一般使用时由于递归实现的常数过大,我们一般使用的是迭代实现的快速傅里叶变换。

【例题】[Luogu3803] 多项式乘法 评测记录

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
#include <cstdio>
#include <cmath>

#define N 2100010
const double pi(acos(-1));

struct comp {
double re,im;
inline comp operator+(const comp&x)const{
return(comp){re+x.re,im+x.im};
}
inline comp operator-(const comp&x)const{
return(comp){re-x.re,im-x.im};
}
inline comp operator*(const comp&x)const{
return(comp){re*x.re-im*x.im,re*x.im+im*x.re};
}
}a[N],b[N];
inline void swap(comp&a,comp&b) {
comp tmp=a; a=b,b=tmp;
}

int lmt,l,r[N];
inline void getRev(int n) {
lmt=1,l=0;
while(lmt<=n) lmt<<=1,++l;
for(int i=1;i<lmt;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}

inline void DFT(comp*a) {
comp wt,w,x,y;
for(int i=0;i<lmt;++i)
if(i<r[i]) swap(a[i],a[r[i]]);
for(int i=1;i<lmt;i<<=1) {
wt=(comp){cos(pi/i),sin(pi/i)};
for(int j=0,step=i<<1;j<lmt;j+=step) {
w=(comp){1,0};
for(int k=0;k<i;++k,w=w*wt) {
x=a[j+k],y=w*a[i+j+k];
a[j+k]=x+y,a[i+j+k]=x-y;
}
}
}
}

inline void IDFT(comp*a) {
comp wt,w,x,y;
for(int i=0;i<lmt;++i)
if(i<r[i]) swap(a[i],a[r[i]]);
for(int i=1;i<lmt;i<<=1) {
wt=(comp){cos(pi/i),-sin(pi/i)};
for(int j=0,step=i<<1;j<lmt;j+=step) {
w=(comp){1,0};
for(int k=0;k<i;++k,w=w*wt) {
x=a[j+k],y=w*a[i+j+k];
a[j+k]=x+y,a[i+j+k]=x-y;
}
}
}
for(int i=0;i<lmt;++i)
a[i].re/=lmt,a[i].im/=lmt;
}

int n,m;

int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i)
scanf("%lf",&a[i].re);
for(int i=0;i<=m;++i)
scanf("%lf",&b[i].re);
getRev(n+m+2);
DFT(a); DFT(b);
for(int i=0;i<lmt;++i)
a[i]=a[i]*b[i];
IDFT(a);
for(int i=0;i<=n+m;++i)
printf("%.0lf ",a[i].re+0.1);
return 0;
}

优化

三次变两次

用刚刚所讲的内容做多项式乘法,需要使用三次 FFT\text{FFT} :两次 DFT\text{DFT} 和一次 IDFT\text{IDFT}

事实上有一个优化可以优化到只做一次 DFT\text{DFT} 和一次 IDFT\text{IDFT}

我们把 G(x)G(x) 的系数放到 F(x)F(x) 的虚部里,然后对 F(x)F(x) 进行 DFT\text{DFT} ,将 F(x)F(x) 平方后进行 IDFT\text{IDFT} ,将虚部的系数除以 22 即是答案。

考虑为什么这样是对的:对于 aabb ,有 (a+bi)2=a2b2+(2abi)(a+bi)^2=a^2-b^2+(2abi) ,因此虚部系数即为答案两倍。

【例题】[Luogu3803] 多项式乘法 评测记录

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
#include <cstdio>
#include <cmath>

#define N 2100010
const double pi(acos(-1));

struct comp {
double re,im;
inline comp operator+(const comp&x)const{
return(comp){re+x.re,im+x.im};
}
inline comp operator-(const comp&x)const{
return(comp){re-x.re,im-x.im};
}
inline comp operator*(const comp&x)const{
return(comp){re*x.re-im*x.im,re*x.im+im*x.re};
}
}a[N];
inline void swap(comp&a,comp&b) {
comp tmp=a; a=b,b=tmp;
}

int lmt,l,r[N];
inline void getRev(int n) {
lmt=1,l=0;
while(lmt<=n) lmt<<=1,++l;
for(int i=1;i<lmt;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}

inline void DFT(comp*a) {
comp wt,w,x,y;
for(int i=0;i<lmt;++i)
if(i<r[i]) swap(a[i],a[r[i]]);
for(int i=1;i<lmt;i<<=1) {
wt=(comp){cos(pi/i),sin(pi/i)};
for(int j=0,step=i<<1;j<lmt;j+=step) {
w=(comp){1,0};
for(int k=0;k<i;++k,w=w*wt) {
x=a[j+k],y=w*a[i+j+k];
a[j+k]=x+y,a[i+j+k]=x-y;
}
}
}
}

inline void IDFT(comp*a) {
comp wt,w,x,y;
for(int i=0;i<lmt;++i)
if(i<r[i]) swap(a[i],a[r[i]]);
for(int i=1;i<lmt;i<<=1) {
wt=(comp){cos(pi/i),-sin(pi/i)};
for(int j=0,step=i<<1;j<lmt;j+=step) {
w=(comp){1,0};
for(int k=0;k<i;++k,w=w*wt) {
x=a[j+k],y=w*a[i+j+k];
a[j+k]=x+y,a[i+j+k]=x-y;
}
}
}
for(int i=0;i<lmt;++i)
a[i].re/=lmt,a[i].im/=lmt;
}

int n,m;

int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i)
scanf("%lf",&a[i].re);
for(int i=0;i<=m;++i)
scanf("%lf",&a[i].im);
getRev(n+m+2);
DFT(a);
for(int i=0;i<lmt;++i)
a[i]=a[i]*a[i];
IDFT(a);
for(int i=0;i<=n+m;++i)
printf("%.0lf ",a[i].im/2+0.1);
return 0;
}

MTT

暂咕

快速数论变换

如果是在模意义下做运算,我们还有一种算法可以胜任,那就是快速数论变换(NTT)

考虑到做快速傅里叶变换时我们将单位根代入求出点值,如果模意义下有与单位根性质类似的数,是否可以代替单位根完成一样的工作呢?答案是肯定的,这一类数是——原根 gg

原根

我们先给出的定义:

apa\perp pp>1p>1aapp 的阶为满足 an1(modp)a^n\equiv1\pmod{p} 的最小的 nn ,记为 δp(a)\delta_p(a)

pp 为正整数, aa 是整数,且 δp(a)=φ(p)\delta_p(a)=\varphi(p) ,则 aa 为模 pp 的一个原根。( φ(n)\varphi(n) 为小于等于 nn 的数中与 nn 互质的数的个数)

原根有一些很有意思的性质:

  1. 若模 pp 存在原根,则其一定有 φ(φ(p))\varphi(\varphi(p)) 个原根。
  2. pp 为质数, ggpp 的一个原根,则 gi(modp)g^i\pmod{p} 对于不同的 i[1,p)i\in[1,p) 值都不同

同时原根在模意义下与单位根有相同的性质,因此我们可以使用原根代替单位根在模意义下做快速数论变换。

代码实现

类似快速傅里叶变换,只把里面的单位根相应地换成原根,并注意取模即可。

【例题】[Luogu3803] 多项式乘法 评测记录

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
#include <cstdio>

#define MOD 998244353
#define N 2100010
typedef long long i64;

inline void swap(int&a,int&b) {
int tmp(a); a=b,b=tmp;
}

inline int pow(int a,int b) {
int ans(1);
while(b) {
ans=b&1?(i64)ans*a%MOD:ans;
a=(i64)a*a%MOD; b>>=1;
}
return ans;
}

int lmt,l,r[N];
inline void getRev(int n) {
lmt=1,l=0;
while(lmt<=n) lmt<<=1,++l;
for(int i=1;i<lmt;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
}

inline void DFT(int*a) {
int wt,w,x,y;
for(int i=0;i<lmt;++i)
if(i<r[i]) swap(a[i],a[r[i]]);
for(int i=1;i<lmt;i<<=1) {
wt=pow(3,(MOD-1)/(i<<1));
for(int j=0,step=i<<1;j<lmt;j+=step) {
w=1;
for(int k=0;k<i;++k,w=(i64)w*wt%MOD) {
x=a[j+k],y=(i64)w*a[i+j+k]%MOD;
a[j+k]=(x+y)%MOD,a[i+j+k]=(x-y+MOD)%MOD;
}
}
}
}

inline void IDFT(int*a) {
int wt,w,x,y;
for(int i=0;i<lmt;++i)
if(i<r[i]) swap(a[i],a[r[i]]);
for(int i=1;i<lmt;i<<=1) {
wt=pow(332748118,(MOD-1)/(i<<1));
for(int j=0,step=i<<1;j<lmt;j+=step) {
w=1;
for(int k=0;k<i;++k,w=(i64)w*wt%MOD) {
x=a[j+k],y=(i64)w*a[i+j+k]%MOD;
a[j+k]=(x+y)%MOD,a[i+j+k]=(x-y+MOD)%MOD;
}
}
}
int bk(pow(lmt,MOD-2));
for(int i=0;i<lmt;++i)
a[i]=(i64)a[i]*bk%MOD;
}

int n,m,a[N],b[N];

int main() {
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i)
scanf("%d",a+i);
for(int i=0;i<=m;++i)
scanf("%d",b+i);
getRev(n+m+2);
DFT(a); DFT(b);
for(int i=0;i<lmt;++i)
a[i]=(i64)a[i]*b[i]%MOD;
IDFT(a);
for(int i=0;i<=n+m;++i)
printf("%d ",a[i]);
return 0;
}

预处理原根

我们来考虑一个优化:在 NTT\text{NTT} 的过程中我们多次使用了原根的各次幂,如果我们能将原根的各次幂预处理出来,而不是每次使用都计算一遍,就能有很大的常数优化!

在多项式乘法中我们只做了 33NTT\text{NTT} ,所以看上去常数优化并不明显。但是在后面的内容中,随着 NTT\text{NTT} 次数的增多,预处理原根的优化效果将愈发明显。

【例题】[Luogu3803] 多项式乘法 评测记录1.83s1.3s1.83s\rightarrow1.3s

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
#include <cstdio>
#include <algorithm>
using std::reverse;

#define MOD 998244353
#define N 2100010
typedef long long i64;
typedef unsigned long long u64;

inline int pow(int a,int b) {
int ans(1);
while(b) {
ans=b&1?(i64)ans*a%MOD:ans;
a=(i64)a*a%MOD; b>>=1;
}
return ans;
}

int lmt(1),r[N],w[N];
inline int getLen(int n) {
return 1<<(32-__builtin_clz(n));
}

inline void init(int n) {
int l(0);
while(lmt<=n) lmt<<=1,++l;
for(int i=1;i<lmt;++i)
r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
int wn(pow(3,(MOD-1)>>l));
w[lmt>>1]=1;
for(int i=(lmt>>1)+1;i<lmt;++i)
w[i]=(i64)w[i-1]*wn%MOD;
for(int i=(lmt>>1)-1;i;--i)
w[i]=w[i<<1];
lmt=l;
}

inline void DFT(int*a,int l) {
static u64 tmp[N];
int u(lmt-__builtin_ctz(l)),t;
for(int i=0;i<l;++i)
tmp[r[i]>>u]=a[i];
for(int i=1;i<l;i<<=1)
for(int j=0,step=i<<1;j<l;j+=step)
for(int k=0;k<i;++k) {
t=tmp[i+j+k]*w[i+k]%MOD;
tmp[i+j+k]=tmp[j+k]+MOD-t;
tmp[j+k]+=t;
}
for(int i=0;i<l;++i)
a[i]=tmp[i]%MOD;
}

inline void IDFT(int*a,int l) {
reverse(a+1,a+l); DFT(a,l);
int bk(MOD-(MOD-1)/l);
for(int i=0;i<l;++i)
a[i]=(i64)a[i]*bk%MOD;
}

int n,m,a[N],b[N],l;

int main() {
scanf("%d%d",&n,&m);
init(n+m+2);
for(int i=0;i<=n;++i)
scanf("%d",a+i);
for(int i=0;i<=m;++i)
scanf("%d",b+i);
l=getLen(n+m+2);
DFT(a,l); DFT(b,l);
for(int i=0;i<l;++i)
a[i]=(i64)a[i]*b[i]%MOD;
IDFT(a,l);
for(int i=0;i<=n+m;++i)
printf("%d ",a[i]);
return 0;
}

分治 FFT

【例题】[Luogu4721] 分治 FFT

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 gn=i=1ngnifig_n=\sum_{i=1}^{n}{g_{n-i}f_i} , g0=1g_0=1

暂咕


多项式求导 & 积分

多项式求导

稍微会一点微积分的应该都知道幂法则:

d(xn)dx=nxn1\frac{d(x^n)}{dx}=nx^{n-1}

以及和法则:

d(f(x)+g(x))dx=d(f(x))dx+d(g(x))dx\frac{d(f(x)+g(x))}{dx}=\frac{d(f(x))}{dx}+\frac{d(g(x))}{dx}

因此多项式求导就是将每一项求导后相加,时间复杂度 Θ(n)\Theta(n)

代码实现

1
2
3
4
5
inline void getDer(int*a,int*b,int deg) {
for(int i=0;i+1<deg;++i)
b[i]=(i64)a[i+1]*(i+1)%MOD;
b[deg-1]=0;
}

多项式积分

与求导同理,有积分公式:

xndx=xn+1n+1+C\int x^ndx=\frac{x^{n+1}}{n+1}+C

由不定积分的性质有:

(f(x)+g(x))dx=f(x)dx+g(x)dx\int(f(x)+g(x))dx=\int f(x)dx+\int g(x)dx

于是多项式积分也可以每一项积分后相加,时间复杂度 Θ(n)\Theta(n)

代码实现

1
2
3
4
5
inline void getInt(int*a,int*b,int deg) {
for(int i=1;i<deg;++i)
b[i]=(i64)a[i-1]*inv[i]%MOD;
b[0]=0;
}

多项式求逆

【例题】[Luogu4238] 多项式求逆

我们已经知道,当 f00f_0\ne0F(x)F(x) 存在唯一乘法逆元。接下来我们就探讨一下如何求得一个多项式的乘法逆元。

考虑已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 F(x)G(x)1(modxn)F(x)G(x)\equiv1\pmod{x^n}

如果 F(x)F(x) 只有常数项,答案显然是常数项的乘法逆元,在此基础上,我们考虑倍增地求出整个 G(x)G(x)

假设我们已经求出了一个 G(x)G'(x) 满足:

F(x)G(x)1(modxn2)F(x)G'(x)\equiv1\pmod{x^{\lceil\frac{n}{2}\rceil}}

由于 F(x)G(x)1(modxn)F(x)G(x)\equiv1\pmod{x^n} ,有:

(G(x)G(x))0(modxn2)(G'(x)-G(x))\equiv0\pmod{x^{\lceil\frac n2\rceil}}

两边同时平方,有:

(G(x)G(x))20(modxn)(G'(x)-G(x))^2\equiv0\pmod{x^n}

G(x)2+G(x)22G(x)G(x)0(modxn)G'(x)^2+G(x)^2-2G'(x)G(x)\equiv0\pmod{x^n}

F(x)G(x)2+G(x)2G(x)0(modxn)F(x)G'(x)^2+G(x)-2G'(x)\equiv0\pmod{x^n}

G(x)2G(x)F(x)G(x)2(modxn)G(x)\equiv2G'(x)-F(x)G'(x)^2\pmod{x^n}

于是我们得到了从 G(x)G'(x) 推到 G(x)G(x) 的方式。

时间复杂度 T(n)=T(n2)+Θ(nlogn)T(n)=T(\frac n2)+\Theta(n\log n) ,由主定理有 T(n)=Θ(nlogn)T(n)=\Theta(n\log n)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void getInv(int*a,int*b,int deg) {
if(deg==1) b[0]=pow(a[0],MOD-2);
else {
static int tmp[N];
getInv(a,b,(deg+1)>>1);
int len=getLen(deg<<1);
for(int i=0;i<deg;++i)
tmp[i]=a[i];
for(int i=deg;i<len;++i)
tmp[i]=0;
DFT(tmp,len); DFT(b,len);
for(int i=0;i<len;++i)
b[i]=(2ll-(i64)b[i]*tmp[i]%MOD+MOD)%MOD*b[i]%MOD;
IDFT(b,len);
for(int i=deg;i<len;++i)
b[i]=0;
}
}

多项式开根

【例题】[Luogu5205] 多项式开根

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)2F(x)(modxn)G(x)^2\equiv F(x)\pmod{x^n}

同样如果 F(x)F(x) 只有常数项,答案显然是常数项的二次剩余,在此基础上我们仍然考虑倍增。

假设我们已经求出了一个 G(x)G'(x) 满足:

G(x)2F(x)(modxn2)G'(x)^2\equiv F(x)\pmod{x^{\lceil\frac n2\rceil}}

则有:

G(x)2G(x)20(mod()xn2)G'(x)^2-G(x)^2\equiv0\pmod(x^{\lceil\frac n2\rceil})

G(x)4+G(x)42G(x)2G(x)20(modxn)G'(x)^4+G(x)^4-2G'(x)^2G(x)^2\equiv0\pmod{x^n}

G(x)4+G(x)4+2G(x)2G(x)24G(x)2G(x)2(modxn)G'(x)^4+G(x)^4+2G'(x)^2G(x)^2\equiv4G'(x)^2G(x)^2\pmod{x^n}

G(x)2+G(x)22G(x)G(x)(modxn)G'(x)^2+G(x)^2\equiv2G'(x)G(x)\pmod{x^n}

G(x)G(x)2+G(x)22G(x)(xn)G(x)\equiv\frac{G'(x)^2+G(x)^2}{2G'(x)}\pod{x^n}

因此有 G(x)G(x)2+F(x)2G(x)(modxn)G(x)\equiv\frac{G'(x)^2+F(x)}{2G'(x)}\pmod{x^n} ,于是可以倍增,时间复杂度同样是 Θ(nlogn)\Theta(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
/// 假设常数项为 1
void getSqrt(int*a,int*b,int deg) {
if(deg==1) b[0]=1;
else {
static int tmpA[N],tmpB[N];
getSqrt(a,b,(deg+1)>>1);
getInv(b,tmpB,deg);
int len=getLen(deg<<1);
for(int i=0;i<deg;++i)
tmpA[i]=a[i];
for(int i=deg;i<len;++i)
tmpA[i]=0;
DFT(tmpA,len); DFT(tmpB,len);
for(int i=0;i<len;++i)
tmpB[i]=(i64)tmpB[i]*tmpA[i]%MOD;
IDFT(tmpB,len);
for(int i=0;i<deg;++i)
b[i]=(i64)inv[2]*(b[i]+tmpB[i])%MOD;
for(int i=0;i<len;++i)
tmpB[i]=0;
}
}

多项式反三角函数(选学)

【例题】[Luogu5265] 多项式反三角函数

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)arcsin(F(x))(modxn)G(x)\equiv\arcsin(F(x))\pmod{x^n}

如果你对导数表熟悉,你会想起反三角函数的导函数十分地…单纯!

d(arcsin(x))dx=11x2\frac{d(\arcsin(x))}{dx}=\frac{1}{\sqrt{1-x^2}}

于是我们考虑先求出 G(x)G(x) 的导数再对其积分:

G(x)F(x)1F(x)2dx(modxn)G(x)\equiv\int{\frac{F'(x)}{\sqrt{1-F(x)^2}}dx}\pmod{x^n}

arccos\arccosarctan\arctan 也是同理,时间复杂度 Θ(nlogn)\Theta(n\log n)


多项式对数函数

【例题】[Luogu4725] 多项式对数函数

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)ln(F(x))(modxn)G(x)\equiv\ln(F(x))\pmod{x^n}

组合意义

什么?你从来没听说过多项式还可以取对数?你不知道它的意义?别着急。

假设我们有一个 F(x)F(x) 满足:

F(x)=i=0G(x)ii!F(x)=\sum_{i=0}^{\infty}{\frac{G(x)^i}{i!}}

G(x)G(x) 的第 ii 项系数表示集合大小为 ii 的方案数,则 F(x)F(x) 的第 ii 项表示选出若干个集合,使其大小之和恰好为 ii 的方案数。

你会发现 i=0G(x)ii!\sum_{i=0}^{\infty}{\frac{G(x)^i}{i!}} 恰好为 eG(x)e^{G(x)} 的麦克劳林级数,于是有:

F(x)=eG(x)G(x)=ln(F(x))F(x)=e^{G(x)}~~\Longleftrightarrow~~G(x)=\ln(F(x))

计算方法

对两边求导,有:

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

于是得到:

G(x)F(x)F(x)dx(modxn)G(x)\equiv\int{\frac{F'(x)}{F(x)}dx}\pmod{x^n}

只要将 F(x)F(x) 分别求导和求逆后相乘,将其结果积分即可得到 G(x)G(x) ,时间复杂度 Θ(nlogn)\Theta(n\log n)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// 假设常数项为 1
inline void getLn(int*a,int*b,int deg) {
static int tmp[N];
getInv(a,tmp,deg);
getDer(a,b,deg);
int len=getLen(deg<<1);
DFT(tmp,len); DFT(b,len);
for(int i=0;i<len;++i)
tmp[i]=(i64)tmp[i]*b[i]%MOD;
IDFT(tmp,len);
getInt(tmp,b,deg);
for(int i=deg;i<len;++i)
b[i]=0;
for(int i=0;i<len;++i)
tmp[i]=0;
}

多项式指数函数

【例题】[Luogu4726] 多项式指数函数

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)eF(x)(modxn)G(x)\equiv e^{F(x)}\pmod{x^n}

多项式牛顿迭代

想必大家都知道求函数零点除了二分还有一个著名的方法叫牛顿迭代法,也即,求解 f(x)=0f(x)=0 ,可以选取一个 x0x_0 作为根的近似值,然后令 xi+1=xif(xi)f(xi)x_{i+1}=x_i-\frac{f(x_i)}{f'(x_i)}

事实上,牛顿迭代法也可以用在多项式身上!

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 F(G(x))0(modxn)F(G(x))\equiv0\pmod{x^n}

假设我们已经求出了一个 G0(x)G_0(x) 满足:

F(G0(x))0(modxn2)F(G_0(x))\equiv0\pmod{x^{\lceil\frac n2\rceil}}

F(x)F(x) 泰勒展开后取前两项,则有:

F(G(x))F(G0(x))+F(G0(x))(G(x)G0(x))(modxn)F(G(x))\equiv F(G_0(x))+F'(G_0(x))(G(x)-G_0(x))\pmod{x^n}

可以解出 G(x)G0(x)F(G0(x))F(G0(x))(modxn)G(x)\equiv G_0(x)-\frac{F(G_0(x))}{F'(G_0(x))}\pmod{x^n} ,与原来的牛顿迭代公式几乎一致!

事实上之前的多项式开根我们就是用多项式牛顿迭代求解的!

计算方法

现在回归正题,由 G(x)eF(x)(modxn)G(x)\equiv e^{F(x)}\pmod{x^n}

ln(G(x))F(x)0(modxn)\ln(G(x))-F(x)\equiv0\pmod{x^n}

我们相当于要求上面的方程的零点,对左边求导,可得 1G(x)\frac{1}{G(x)} ,代入牛顿迭代公式:

G(x)G0(x)ln(G0(x))F(x)1G0(x)(modxn)G(x)\equiv G_0(x)-\frac{\ln(G_0(x))-F(x)}{\frac{1}{G_0(x)}}\pmod{x^n}

G(x)G0(x)(1ln(G0(x))+F(x))(modxn)G(x)\equiv G_0(x)(1-\ln(G_0(x))+F(x))\pmod{x^n}

像前面一样倍增即可,时间复杂度 Θ(nlogn)\Theta(n\log n)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// 假设常数项为 0
void getExp(int*a,int*b,int deg) {
if(deg==1) b[0]=1;
else {
static int tmp[N];
getExp(a,b,(deg+1)>>1);
getLn(b,tmp,deg);
int len=getLen(deg<<1);
for(int i=0;i<deg;++i)
tmp[i]=(a[i]-tmp[i]+MOD)%MOD;
for(int i=deg;i<len;++i)
tmp[i]=0;
++tmp[0];
DFT(tmp,len); DFT(b,len);
for(int i=0;i<len;++i)
b[i]=(i64)b[i]*tmp[i]%MOD;
IDFT(b,len);
for(int i=deg;i<len;++i)
b[i]=tmp[i]=0;
}
}

多项式双曲函数(选学)

【例题】[Luogu5494] 多项式双曲函数

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)sinh(F(x))(modxn)G(x)\equiv\sinh(F(x))\pmod{x^n}

众所周知 sinh(x)=exex2\sinh(x)=\frac{e^x-e^{-x}}{2} ,于是我们就可以直接计算:

G(x)eF(x)eF(x)2(modxn)G(x)\equiv\frac{e^{F(x)}-e^{-F(x)}}{2}\pmod{x^n}

cosh\coshsech\operatorname{sech} 也是同理,时间复杂度 Θ(nlogn)\Theta(n\log n)


多项式三角函数(选学)

【例题】[Luogu5264] 多项式三角函数

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)sin(F(x))(modxn)G(x)\equiv\sin(F(x))\pmod{x^n}

这个看起来好像不是很好做:三角函数的导数仍然是三角函数,而其本身也没有什么容易计算的变形式。

所以,接下来的一切全部都是膜法!

想到三角函数,应该有不少人会想起 Euler\text{Euler} 公式:

eiθ=cos(θ)+isin(θ)e^{i\theta}=\cos(\theta)+i\sin(\theta)

θ\thetaθ-\theta 替换,可得:

eiθ=cos(θ)isin(θ)e^{-i\theta}=\cos(\theta)-i\sin(\theta)

两式相减,有:

eiθeiθ=2isin(θ)sin(θ)=eiθeiθ2i\begin{aligned} e^{i\theta}-e^{-i\theta}&=2i\sin(\theta) \\ \sin(\theta)&=\frac{e^{i\theta}-e^{-i\theta}}{2i} \end{aligned}

于是有:

G(x)eiF(x)eiF(x)2i(modxn)G(x)\equiv\frac{e^{iF(x)}-e^{-iF(x)}}{2i}\pmod{x^n}

至于 ii 的取值,由于 i21i^2\equiv-1 ,解一个二次剩余即可算出来。

同理,也有:

cos(θ)=eiθ+eiθ2cos(\theta)=\frac{e^{i\theta}+e^{-i\theta}}{2}

于是可以计算,时间复杂度 Θ(nlogn)\Theta(n\log n)


多项式快速幂

【例题】[Luogu5245] 多项式快速幂

已知多项式 F(x)F(x) ,要求出一个多项式 G(x)G(x) 满足 G(x)F(x)k(modxn)G(x)\equiv F(x)^k\pmod{x^n}

类似普通快速幂一样做,有一个很容易想的 Θ(nlognlogk)\Theta(n\log n\log k) 算法。

但是仔细思考一波,发现 F(x)kekln(F(x))(modxn)F(x)^k\equiv e^{k\ln(F(x))}\pmod{x^n} ,于是问题变得简单。

直接将 F(x)F(x) 求对数后系数乘上 kk ,再做 Exp\text{Exp} 就是答案,时间复杂度 Θ(nlogn)\Theta(n\log n)


多项式高阶前缀和 & 差分

【例题】[Luogu5488] 差分与前缀和

已知多项式 F(x)F(x) ,要求出其系数的 kk 阶前缀和或差分。

多项式高阶前缀和

考略一阶前缀和的系数:

[xi]G1(x)=j=1ifj[x^i]G_1(x)=\sum_{j=1}^{i}{f_j}

你会发现这就是 F(x)F(x) 和一个系数为全 11 的多项式的离散卷积,即相当于 G1(x)=F(x)1xG_1(x)=\frac{F(x)}{1-x}

因此:

Gk(x)=F(x)(1x)kG_k(x)=\frac{F(x)}{(1-x)^k}

牛顿广义二项式定理告诉我们:

1(1x)k=i=0(ki)(x)i=i=0n1(i+k1k1)xi\begin{aligned} \frac{1}{(1-x)^k}&=\sum_{i=0}^{\infty}{\binom{-k}{i}(-x)^i} \\ &=\sum_{i=0}^{n-1}{\binom{i+k-1}{k-1}x^i} \end{aligned}

于是可以直接计算出这个式子的值,再与 F(x)F(x) 进行离散卷积,时间复杂度 Θ(nlogn)\Theta(n\log n)

多项式高阶差分

与前缀和相反,一阶差分相当于与 1x1-x 进行卷积,因此:

Gk(x)=F(x)(1x)kG_k(x)=F(x)(1-x)^k

使用牛顿二项式定理,有:

(1x)k=i=0k(ki)(x)i=i=0k(1)i(ki)xi\begin{aligned} (1-x)^k&=\sum_{i=0}^{k}{\binom{k}{i}(-x)^i} \\ &=\sum_{i=0}^{k}{(-1)^i\binom{k}{i}x^i} \end{aligned}

发现这个式子只有 k+1k+1 项,并且可以直接计算,时间复杂度 Θ(nlogn)\Theta(n\log n)


多项式除法 & 取模

【例题】[Luogu4512] 多项式除法

已知 nn 次多项式 F(x)F(x)mm 次多项式 G(x)G(x) ,要求出多项式 Q(x),R(x)Q(x),R(x) 满足:

F(x)=Q(x)G(x)+R(x)F(x)=Q(x)G(x)+R(x)

Q(x)Q(x)nmn-m 次多项式, R(x)R(x) 次数小于 mm ,保证 m<nm<n

我们令 AR(x)=xnA(x1)A_R(x)=x^nA(x^{-1}) ,可以发现 AR(x)A_R(x) 即为翻转系数后的 A(x)A(x)

来考虑题目的式子:

F(x)=Q(x)G(x)+R(x)F(x1)=Q(x1)G(x1)+R(x1)xnF(x1)=xnQ(x1)G(x1)+xnR(x1)FR(x)=xnmQ(x1)xmG(x1)+xnm+1xm1R(x1)FR(x)=QR(x)GR(x)+xnm+1RR(x)FR(x)QR(x)GR(x)(modxnm+1)QR(x)FR(x)GR(x)1(modxnm+1)\begin{aligned} F(x)&=Q(x)G(x)+R(x) \\ F(x^{-1})&=Q(x^{-1})G(x^{-1})+R(x^{-1}) \\ x^nF(x^{-1})&=x^nQ(x^{-1})G(x^{-1})+x^nR(x^{-1}) \\ F_R(x)&=x^{n-m}Q(x^{-1})\cdot x^mG(x^{-1})+x^{n-m+1}\cdot x^{m-1}R(x^{-1}) \\ F_R(x)&=Q_R(x)G_R(x)+x^{n-m+1}R_R(x) \\ F_R(x)&\equiv Q_R(x)G_R(x)\pmod{x^{n-m+1}} \\ Q_R(x)&\equiv F_R(x)G_R(x)^{-1}\pmod{x^{n-m+1}} \end{aligned}

于是将 F(x)F(x)G(x)G(x) 系数翻转,让 FR(x)F_R(x)GR(x)G_R(x) 的逆元做多项式乘法,结果在 modxnm+1\bmod{x^{n-m+1}} 意义下翻转系数即为 Q(x)Q(x) 。又由:

R(x)=F(x)Q(x)G(x)R(x)=F(x)-Q(x)G(x)

可求出 R(x)R(x) ,时间复杂度 Θ(nlogn)\Theta(n\log n)


多项式多点求值

【例题】[Luogu5050] 多项式多点求值

已知多项式 F(x)F(x) 和长为 mm 的序列 aa ,对于 i[1,m]i\in[1,m] 求出 F(ai)F(a_i)

对于常数 x0x_0 ,有:

F(x)F(x0)(mod(xx0))F(x)\equiv F(x_0)\pmod{(x-x_0)}

简单证明一下:设 F(x)=G(x)(xx0)+AF(x)=G(x)(x-x_0)+A ,也即 AAF(x)F(x)xx0x-x_0 取模的结果。

由于 xx0x-x_0 次数为 11AA 必是一个常数,所以有 F(x)A(mod(xx0))F(x)\equiv A\pmod{(x-x_0)}

x=x0x=x_0 代入,有 F(x0)=G(x0)(x0x0)+AF(x_0)=G(x_0)(x_0-x_0)+A ,因此 AF(x0)(mod(xx0))A\equiv F(x_0)\pmod{(x-x_0)}

综上, F(x)F(x0)(mod(xx0))F(x)\equiv F(x_0)\pmod{(x-x_0)}

接下来就是膜法的工作了!

我们考虑分治,假设当前对于 i[l,r]i\in[l,r]F(ai)F(a_i) ,我们令 mid=l+r2mid=\frac{l+r}{2} ,设:

P0=i=lmid(xai)P_0=\prod_{i=l}^{mid}{(x-a_i)}

P1=i=mid+1r(xai)P_1=\prod_{i=mid+1}^{r}{(x-a_i)}

此时我们已经求出 F(x)(modi=lr(xai))F(x)\pmod{\prod_{i=l}^{r}{(x-a_i)}} ,于是只要用当前的结果分别对 P0P_0P1P_1 取模再递归下去即可,由主定理可知时间复杂度 Θ(nlog2n)\Theta(n\log^2n)


线性递推

【例题】[Luogu4723] 线性递推

已知 kk 阶常系数齐次递推数列 aa 的前 kk 项及其递推式,求 aa 的第 nn 项。

暂咕


多项式快速插值

【例题】[Luogu5158] 多项式快速插值

暂咕