数学全家桶 学习笔记
重点是多项式和一些 GF 知识,其他的应该会分篇或简单带过。
gcd/exgcd/裴蜀定理
点击查看代码
int gcd(int x,int y){
return (!y)?x:gcd(y,x%y);
}
int exgcd(int a,int b,int &x,int &y){
if(!b){x=1,y=0;return a;}
int g=exgcd(b,a%b,y,x);
y-=(a/b)*x;return g;
}
逆元
点击查看代码
const int P=1e9+7;
int jc[N],iv[N],ij[N];
void init(int n){
jc[0]=1;for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%P;
iv[1]=1;for(int i=2;i<=n;i++)iv[i]=P-(P/i)*iv[P%i]%P;
ij[0]=1;for(int i=1;i<=n;i++)ij[i]=ij[i-1]*iv[i]%P;
}
CRT/exCRT
BSGS/exBSGS
Lucas/exLucas
莫反
见 莫比乌斯反演 学习笔记。
拉格朗日插值
- P4781 【模板】拉格朗日插值
记 n 个点为 \((a_i,b_i)\),考虑朴素 n^2 做法:\(f(x)=\sum_{i=1}^nb_i\prod_{j\neq i}\frac{x-a_j}{a_i-a_j}\)。
- CF622F The Sum of the k-th Powers
\(O(k\log k)\) 做法:第 i 个点为 \((a_i,b_i)=(i,\sum_{j=1}^i j^k)\),因为 \(b_i\) 的差分是 \(i^k\),是 k 次多项式,所以 \(b_i\) 的表达式是 k+1 次多项式,那么我们至少要找 k+2 个点。由于 \(a_i=i\) 是连续的,后面那坨可以简单维护。预处理 \(b_i\) 是复杂度瓶颈。
FFT/NTT
- 关于单位根
一些基本的复数知识。
在复平面上,模长为 1 的复数叫单位复数,所有单位复数组成了单位圆。已知复数乘法是辐角相加、模长相乘,那么在单位圆上的点相乘还是在单位圆上。所以对于 \(x^n=1\) 这个方程,它的解应该都在单位圆上,且辐角都是 \(\frac{360}{n}\) 的倍数,这样的解一共有 n 个,我们把这 n 个复数叫做 n 次单位根,将其中辐角最小的(1 除外)记作 \(\omega_n\)。
在 fft 中需要用到两个引理:
-
消去引理:\(\omega_{kn}^{km}=\omega_n^m\)
-
折半引理:\((\omega_{2n}^{k+n})^2=\omega_{2n}^{2k}=\omega_n^k\)(其实单位根有更强的结论 \(\omega_{2n}^{k+n}=-\omega_{2n}^k\),但在 fft 中只需要满足前面的)
证明都比较显然,略去了。
还有一个会用的:\(\omega_n^k=\cos(\frac{2k\pi}{n})+i\sin(\frac{2k\pi}{n})\)
证明就是设 \(\omega_n^k=\cos\theta+i\sin\theta\),注意到 \((\omega_n^k)^n=(\cos\theta+i\sin\theta)^n=\cos n\theta+i\sin n\theta\),后面的等式可以通过归纳 + 和角公式得到,然后上式根据单位根的定义等于 \(1+0i\),即 \(\cos n\theta=1,\sin n\theta=0\),那么 \(n\theta=2k\pi\),\(\theta=\frac{2k\pi}{n}\),得证。
- 关于多项式
一个多项式 \(F(x)=\sum\limits_{i=0}^{n-1}a_ix^i\) 有两种表示方法:
-
系数表示。即每一项的系数 \([a_0,a_1\ldots a_{n-1}]\)。
-
点值表示。即在 n 个点上 \(F(x)\) 的取值,即 \([(v_0,F(v_0)),(v_1,F(v_1))\ldots (v_{n-1},F(v_{n-1}))]\)。
显然,如果多项式 \(H(x)=F(x)\times G(x)\),那么 \(H(v_i)=F(v_i)\times G(v_i)\)。容易发现,虽然系数表示更直观,但点值表示在某些时候更方便。所以我们要做的就是在这两种表示方式之间来回切换。
- 关于 fft
下文默认 \(n=2^w\)。设 \(F(x)=\sum\limits_{i=0}^{n-1}a_ix^i\)。假设我们已经知道了 \(A=[a_i]\),思考怎么快速求出 \(F(x)\) 的点值表示。考虑把 \([a_i]\) 分成两组 \(A^{[0]}=[a_0,a_2\ldots a_{n-2}]\) 和 \(A^{[1]}=[a_1,a_3\ldots a_{n-1}]\),此时显然 \(F(x_0)=F^{[0]}(x_0^2)+x_0F^{[1]}(x_0^2)\)。
从这个思路出发,我们考虑分治。但是因为 \(F^{[0]},F^{[1]}\) 的点值表示都只有 \(\frac{n}{2}\) 项,所以我们只能合并出 \(F\) 的 \(\frac{n}{2}\) 个点值 \(\ldots\) 吗?考虑取一些特殊的点值,比如 \(v_i=\omega_n^i\)。设 \(j=i+\frac{n}{2}\),代入原式可以得到:
可以从 \(F^{[0]},F^{[1]}\) 的点值表示推出 \(F\) 的点值表示之后,就可以递归求解了。当 n=1 时,\(F(x_0)=a_0\)。
- 关于非递归
上面的写法很慢,因为递归。不妨仔细观察递归的过程(每次将一行 \(A\) 分成 \(A^{[0]}\) 和 \(A^{[1]}\),将它们用 | 隔开):
F(8): a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7]
F(4): a[0] a[2] a[4] a[6] | a[1] a[3] a[5] a[7]
F(2): a[0] a[4] | a[2] a[6] | a[1] a[5] | a[3] a[7]
F(1): a[0] | a[4] | a[2] | a[6] | a[1] | a[5] | a[3] | a[7]
观察最后一行的编号,发现如果从 0 开始数,第 i 个位置上的 \(a_j\),这个 j 和 i 的二进制表示刚好互为逆序。那么现在我们就解决了非递归版的 FFT。
- 关于 ifft
考虑从点值表示转回系数表示,这个过程就是 ifft。不妨设 \([y_i]\) 为 \(F(x)\) 的点值表示,\(c_i=\sum\limits_{j=0}^{n-1}y_i(\omega_n^{-i})^j\),推一下式子:
当 \(\omega_n^{k-i}\neq 1\) 时,后面那坨等于 \(\dfrac{(\omega_n^{k-i})^n-1}{\omega_n^{k-i}-1}=0\),所以 \(c_i=a_in\)。
- 关于 ntt
注意到 fft 虽然很方便,但是它涉及到了实数运算,不能取模。思考为什么会用实数?因为运算涉及到了单位根。那能不能找到一些整数,使它们也满足单位根的几个性质呢?答案是肯定的,考虑原根。
以模数为 p=998244353 为例,此时原根 g=3。记 \(g_n=g^{\frac{p-1}{n}}\),令 \(w_n\gets g_n\),根据定义,显然它能满足上述两个引理,所以做完了。这就是 ntt。
等下,万一 \((p-1)\bmod n\neq 0\) 呢?注意到 \(998244353-1=2^{23}\times 7\times 17\),且 n 已经被调整为 2 的幂次了,所以当 \(n\le 2^{23}\) 时都可以除尽捏。原根表。
- 关于任意模数 ntt(mtt)
有的时候,模数是题目中给定的,甚至可能不是质数,怎么办捏?考虑自己取一些模数跑 ntt,然后 crt 合并即可。
具体地,我们取一些模数 \([p_1\ldots p_k]\) 使得答案多项式的系数在取模前不会超过 \(\prod p_i\)。一般来说取 3 个质数(998244353,469762049,1004535809,原根都是 3)就够了。对每个 \(p_i\) 都求出答案对 \(p_i\) 取模的结果,利用 crt 就可以求出答案对 \(\prod p_i\) 取模的结果,再对给定模数取模即可。
- 关于 fft/ntt 的分治
例题 1:已知 \(f_0=1\),\(f_i=\sum\limits_{j=0}^{i-1}f_jg_{i-j}\),求 \(f_{0\ldots n-1}\)。
考虑 cdq 分治,对 \(mid<x\le r\),\(f_{l\ldots mid}\) 对它的影响 \(w_x=\sum\limits_{j=l}^{mid}f_jg_{x-j}=\sum\limits_{j=0}^{mid-l}f_{j+l}g_{x-j-l}\),记 \(a_i=f_{i+l},b_i=g_{i-1}\),则 \(w_x=(a\times b)_{x-l-1}\),直接卷就行,复杂度 \(\mathcal{O}(n\log^2 n)\),被多项式求逆吊打。
例题 2:求一多项式 \(f(x)=\prod_{i=1}^m(x^{a_i}+1)\),\(\sum a_i=n\)。因为贡献和是 \(\sum\limits_{i=1}^m a_i(m-i+1)\),直接从左卷到右最劣是 \(\mathcal{O}(nm\log n)\)。考虑分治卷积,每次先递归 [l,mid][mid+1,r] 再把这两部分卷起来得到 [l,r] 的答案,递归树深度不超过 \(\log n\) 层,复杂度 \(\mathcal{O}(n\log^2 n)\)。但是这样不太好写啊,注意到这不就是我们合并果子吗,直接开个优先队列维护就行了。

浙公网安备 33010602011771号