数学全家桶 学习笔记

重点是多项式和一些 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 中需要用到两个引理:

  1. 消去引理:\(\omega_{kn}^{km}=\omega_n^m\)

  2. 折半引理:\((\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\) 有两种表示方法:

  1. 系数表示。即每一项的系数 \([a_0,a_1\ldots a_{n-1}]\)

  2. 点值表示。即在 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(v_i)=F^{[0]}((\omega_n^i)^2)+\omega_n^iF^{[1]}((\omega_n^i)^2)=F^{[0]}(\omega_{\frac{n}{2}}^i)+\omega_n^iF^{[1]}(\omega_{\frac{n}{2}}^i) \]

\[F(v_j)=F^{[0]}((\omega_n^j)^2)+\omega_n^jF^{[1]}((\omega_n^j)^2)=F^{[0]}(\omega_{\frac{n}{2}}^i)-\omega_n^iF^{[1]}(\omega_{\frac{n}{2}}^i) \]

可以从 \(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\),推一下式子:

\[\begin{aligned} c_i&=\sum\limits_{j=0}^{n-1}y_j(\omega_n^{-i})^j\\ &=\sum\limits_{j=0}^{n-1}(\omega_n^{-i})^j\sum\limits_{k=0}^{n-1}a_k(\omega_n^j)^k\\ &=\sum\limits_{k=0}^{n-1}a_k\sum\limits_{j=0}^{n-1}(\omega_n^{k-i})^j\\ \end{aligned} \]

\(\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)\)。但是这样不太好写啊,注意到这不就是我们合并果子吗,直接开个优先队列维护就行了。

posted @ 2024-06-15 16:42  xx019  阅读(8)  评论(0)    收藏  举报