多项式 学习笔记

记录多项式以及一些相关数论前置芝士qwq

拉格朗日插值

n n n 个点 ( x i , y i ) (x_i,y_i) (xi,yi) 可以唯一确定一个 n − 1 n-1 n1 次多项式 f ( x ) f(x) f(x),要求出 f ( k ) f(k) f(k) 的值可以 O ( n 3 ) O(n^3) O(n3) 高斯消元,而拉格朗日插值可以做到 O ( n 2 ) O(n^2) O(n2) 求解。

f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n f(x)=a_0+a_1x+a_2x^2+\cdots +a_nx^n f(x)=a0+a1x+a2x2++anxn f ( k ) = a 0 + a 1 k + a 2 k 2 + ⋯ + a n k n f(k)=a_0+a_1k+a_2k^2+\cdots+a_nk^n f(k)=a0+a1k+a2k2++ankn f ( x ) − f ( k ) = a 1 ( x − k ) + a 2 ( x 2 − k 2 ) + ⋯ + a n ( x n − k n ) ≡ 0 ( m o d x − k ) f(x)-f(k)=a_1(x-k)+a_2(x^2-k^2)+\cdots+a_n(x^n-k^n)\equiv0\pmod{x-k} f(x)f(k)=a1(xk)+a2(x2k2)++an(xnkn)0(modxk),则 f ( x ) ≡ f ( k ) ( m o d x − k ) f(x)\equiv f(k)\pmod{x-k} f(x)f(k)(modxk)

n n n 个点都带入方程,可得同余方程组 f ( k ) ≡ y i ( m o d k − x i ) )   ( 1 ≤ i ≤ n ) f(k)\equiv y_i\pmod{k-x_i})\ (1\le i\le n) f(k)yi(modkxi)) (1in)

套用 CRT 的求解,令 M = ∏ i = 1 n ( k − x [ i ] ) , m i = M k − x i M=\prod\limits_{i=1}^n(k-x[i]),m_i=\dfrac{M}{k-x_i} M=i=1n(kx[i]),mi=kxiM,则有:
f ( k ) = ∑ i = 1 n y i × m i × m i − 1 f(k)=\sum\limits_{i=1}^ny_i\times m_i\times m_i^{-1} f(k)=i=1nyi×mi×mi1
  = ∑ i = 1 n y i × ∏ j ≠ i ( k − x j ) × m i − 1 \quad\quad\ =\sum\limits_{i=1}^ny_i\times\prod\limits_{j\ne i}(k-x_j)\times m_i^{-1}  =i=1nyi×j=i(kxj)×mi1
  = ∑ i = 1 n y i × ∏ j ≠ i ( k − x i + x i − x j ) × ∏ j ≠ i 1 x i − x j \quad\quad\ =\sum\limits_{i=1}^ny_i\times\prod\limits_{j\ne i}(k-x_i+x_i-x_j)\times \prod\limits_{j\ne i}\dfrac{1}{x_i-x_j}  =i=1nyi×j=i(kxi+xixj)×j=ixixj1
  = ∑ i = 1 n y i × ∏ j ≠ i k − x j x i − x j \quad\quad\ =\sum\limits_{i=1}^ny_i\times\prod\limits_{j\ne i}\dfrac{k-x_j}{x_i-x_j}  =i=1nyi×j=ixixjkxj

中间在 k − x i k-x_i kxi 剩余系中转化了一下逆元,最后求出的就是拉格朗日插值的式子,在模意义和非模意义下通用。

x i x_i xi 是连续整数的插值

对于 ∀ i = 1 n x i = i \forall_{i=1}^n x_i=i i=1nxi=i,可以将拉格朗日插值的式子进一步简化:

{ ∏ j ≠ i ( i − j ) = ( − 1 ) n − i ( i − 1 ) !   ( n − i ) ! ∏ j ≠ i ( k − j ) = ∏ j = 1 n ( k − j ) k − i \begin{cases}\prod\limits_{j\ne i}(i-j)=(-1)^{n-i}(i-1)!\ (n-i)!\\\prod\limits_{j\ne i}(k-j)=\dfrac{\prod\limits_{j=1}^n (k-j)}{k-i}\end{cases} j=i(ij)=(1)ni(i1)! (ni)!j=i(kj)=kij=1n(kj)
   ⟹    f ( k ) = ∑ i = 1 n y i × ( − 1 ) n − i   ∏ j = 1 n ( k − j ) ( i − 1 ) !   ( n − i ) ! ( k − i ) \implies f(k)=\sum\limits_{i=1}^n y_i\times (-1)^{n-i}\ \dfrac{\prod\limits_{j=1}^n (k-j)}{(i-1)!\ (n-i)!(k-i)} f(k)=i=1nyi×(1)ni (i1)! (ni)!(ki)j=1n(kj)

分子的和分母都可以预处理,于是可以做到 O ( n ) O(n) O(n)

永远记不住的线性求逆元柿子: i n v [ i ] = ( p − p / i ) × i n v [ p % i ] inv[i]=(p-p/i)\times inv[p\%i] inv[i]=(pp/i)×inv[p%i]

重心拉格朗日插值

当需要新加入一个点的时候,用朴素的拉插式子算是 O ( n 2 ) O(n^2) O(n2) 的,但可以推柿子做到 O ( n ) O(n) O(n)

f ( k ) = ∑ i = 1 n y i × ∏ j ≠ i k − x j x i − x j f(k)=\sum\limits_{i=1}^ny_i\times\prod\limits_{j\ne i}\dfrac{k-x_j}{x_i-x_j} f(k)=i=1nyi×j=ixixjkxj
  = ∑ i = 1 n y i × ∏ j = 1 n k − x j ( k − x i ) ∏ j ≠ i ( x i − x j ) \quad\quad\ =\sum\limits_{i=1}^ny_i\times\dfrac{\prod\limits_{j=1}^nk-x_j}{(k-x_i)\prod\limits_{j\ne i}(x_i-x_j)}  =i=1nyi×(kxi)j=i(xixj)j=1nkxj
  = ∏ i = 1 n ( k − x i ) ∑ i = 1 n y i ( k − x i ) ∏ j ≠ i ( x i − x j ) \quad\quad\ =\prod\limits_{i=1}^n(k-x_i)\sum\limits_{i=1}^n\dfrac{y_i}{(k-x_i)\prod\limits_{j\ne i}(x_i-x_j)}  =i=1n(kxi)i=1n(kxi)j=i(xixj)yi

g ( k ) = ∏ i = 1 n ( k − x i ) g(k)=\prod\limits_{i=1}^n(k-x_i) g(k)=i=1n(kxi) t i = ∏ j ≠ i 1 x i − x j t_i=\prod\limits_{j\ne i}\dfrac{1}{x_i-x_j} ti=j=ixixj1,则 f ( k ) = g ( k ) ∑ i = 1 n y i t i k − x i f(k)=g(k)\sum\limits_{i=1}^n\dfrac{y_it_i}{k-x_i} f(k)=g(k)i=1nkxiyiti,每次新增一个点 n n n 可以 O ( 1 ) O(1) O(1) 更新 g ( k ) g(k) g(k) O ( n ) O(n) O(n) t n t_n tn O ( n ) O(n) O(n) 更新 t 1 t_1 t1 t n − 1 t_{n-1} tn1,再 O ( n ) O(n) O(n) 更新 f ( k ) f(k) f(k),这个点就插入成功了。

快速傅里叶变换

前置芝士:

  • 多项式的系数表示法:即常见的函数形式,形如 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n f(x)=a_0+a_1x+a_2x^2+\cdots +a_nx^n f(x)=a0+a1x+a2x2++anxn
  • 多项式的点值表示法:对于一个 n − 1 n-1 n1 次多项式, n n n 个不同的 x x x 代入能得到 n n n 个不同的点 ( x i , y i ) (x_i,y_i) (xi,yi),这 n n n 个点唯一确定了该多项式;
  • 多项式的系数表示和点值表示可以相互转化。

快速傅里叶变换(FFT)就是一种 O ( n log ⁡ n ) O(n\log n) O(nlogn) 将系数表示转化为点值表示,进而求出两个多项式的乘积的方法。

离散傅里叶变换

傅里叶把多项式先填到最高次项(系数可以为 0 0 0)为 n = 2 k n=2^k n=2k 的形式,然后规定点值表示中的点为 n n n 个模长为 1 1 1 的复数,使得这些复数在平面直角坐标系中把单位圆 n n n 等分。

为了方便,将这些点以 ( 1 , 0 ) (1,0) (1,0) 为起点逆时针编号为 0 0 0 n − 1 n-1 n1,第 k k k 个点的坐标为 ( cos ⁡ i n 2 π , sin ⁡ i n 2 π ) (\cos\frac{i}{n}2π,\sin\frac{i}{n}2π) (cosni2π,sinni2π),表示的复数为 w n k w_n^k wnk,其中 w n 1 w_n^1 wn1 叫作 n n n 次单位根。

这种点值选取的方式就是离散傅里叶变换(DFT)。

根据复数和三角函数的一些知识,得到(设 a a a 为常数):

  • w n k = w a n a k w_n^k=w_{an}^{ak} wnk=wanak
  • w n k = − w n k + n 2 w_n^k=-w_{n}^{k+\frac{n}{2}} wnk=wnk+2n
  • w n k = w n k + a n w_n^k=w_n^{k+an} wnk=wnk+an

快速傅里叶变换

在 DFT 的基础上,我们推一些柿子:将 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n − 1 f(x)=a_0+a_1x+a_2x^2+\cdots +a_nx^{n-1} f(x)=a0+a1x+a2x2++anxn1 x x x 的幂次的奇偶拆开并化简:

f ( x ) = ( a 0 + a 2 x 2 + ⋯ + a n − 2 x n − 2 ) + ( a 1 x + a 3 x 3 + ⋯ + a n − 1 x n − 1 ) f(x)=(a_0+a_2x^2+\cdots+a_{n-2}x^{n-2})+(a_1x+a_3x^3+\cdots+a_{n-1}x^{n-1}) f(x)=(a0+a2x2++an2xn2)+(a1x+a3x3++an1xn1)
  = ( a 0 + a 2 ( x 1 ) 2 + ⋯ + a n − 2 ( x n − 2 2 ) 2 ) + x ( a 1 + a 3 ( x 1 ) 2 + ⋯ a n − 2 ( x n − 2 2 ) 2 ) \quad\quad\ =(a_0+a_2(x^1)^2+\cdots+a_{n-2}(x^{\frac{n-2}{2}})^2)+x(a_1+a_3(x^1)^2+\cdots a_{n-2}(x^{\frac{n-2}{2}})^2)  =(a0+a2(x1)2++an2(x2n2)2)+x(a1+a3(x1)2+an2(x2n2)2)

g ( x ) = a 0 + a 2 x + ⋯ + a n − 2 x n − 2 2 g(x)=a_0+a_2x+\cdots+a_{n-2}x^{\frac{n-2}{2}} g(x)=a0+a2x++an2x2n2 h ( x ) = a 1 + a 3 x + ⋯ + a n − 1 x n − 2 2 h(x)=a_1+a_3x+\cdots+a_{n-1}x^{\frac{n-2}{2}} h(x)=a1+a3x++an1x2n2,则 f ( x ) = g ( x 2 ) + x   h ( x 2 ) f(x)=g(x^2)+x\ h(x^2) f(x)=g(x2)+x h(x2)

把 DFT 中选择的点代入:

  • k < n 2 k<\dfrac{n}{2} k<2n f ( w n k ) = g ( w n 2 k ) + w n k   h ( w n 2 k ) = g ( w n 2 k ) + w n k   h ( w n 2 k ) f(w_n^k)=g(w_n^{2k})+w_n^k\ h(w_n^{2k})=g(w_\frac{n}{2}^k)+w_n^k\ h(w_\frac{n}{2}^k) f(wnk)=g(wn2k)+wnk h(wn2k)=g(w2nk)+wnk h(w2nk)
  • k ≥ n 2 k\ge \dfrac{n}{2} k2n f ( w n k ) = g ( w n 2 k + n ) + w n k + n 2   h ( w n 2 k + n ) = g ( w n 2 k ) − w n k   h ( w n 2 k ) f(w_n^k)=g(w_n^{2k+n})+w_n^{k+\frac{n}{2}}\ h(w_n^{2k+n})=g(w_\frac{n}{2}^k)-w_n^k\ h(w_\frac{n}{2}^k) f(wnk)=g(wn2k+n)+wnk+2n h(wn2k+n)=g(w2nk)wnk h(w2nk)

那么这就是分治了,但是由于大常数选手的递归版跑得过慢等原因,代码就不放了/kk

递归本身常数就是比较大的,考虑能不能优化。模拟一下每次选奇偶点分前后两半的过程,发现如果把 i i i 的二进制表示前后翻转所得的数为 j j j,那么递归后原本在位置 i i i 的数会跑到位置 j j j,这就是位逆序变换。因此可以预处理出每个数的最终位置,一点点向上合并即可。

实现的时候,复数可以用 STL 的 complex,预处理 i i i 翻转二进制位所得的数 p o s i pos_i posi,就可以写出 FFT 了:

inline void fft(com *a,com *omg){
	ff(i,0,n-1) if(pos[i]<i) swap(a[pos[i]],a[i]);
	for(int len=2;len<=n;len<<=1){
		int m=len>>1;
		for(int i=0;i<n;i+=len) ff(j,0,m-1){
			com tmp=a[i+j+m]*omg[n/len*j];
			a[i+j+m]=a[i+j]-tmp,a[i+j]+=tmp;
		}	
	}
} 

离散傅里叶逆变换

我们用 FFT 成功加速了系数表示转点值表示的过程,但目的是想加速多项式乘法。FFT 是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的,两个点值表示的多项式相乘是 O ( n ) O(n) O(n) 的,想求乘法我们还需要快速将点值表示转化为系数表示,就需要离散傅里叶逆变换(IDFT)。

假设我们对 f ( x ) f(x) f(x) 进行 DFT之后得到了一系列纵坐标 ( y 0 , y 1 , ⋯   , y n − 1 ) (y_0,y_1,\cdots,y_{n-1}) (y0,y1,,yn1),构造函数 g ( x ) = y 0 + y 1 x + ⋯ + y n − 1 x n − 1 g(x)=y_0+y_1x+\cdots+y_{n-1}x^{n-1} g(x)=y0+y1x++yn1xn1,把 w n 0 , w n − 1 , ⋯   , w n − ( n − 1 ) w_n^0,w_n^{-1},\cdots,w_n^{-(n-1)} wn0,wn1,,wn(n1) 代入得到 ( z 0 , z 1 , ⋯   , z n − 1 ) (z_0,z_1,\cdots,z_{n-1}) (z0,z1,,zn1)

z k = ∑ i = 0 n − 1 y i × ( w n − k ) i z_k=\sum\limits_{i=0}^{n-1}y_i\times(w_n^{-k})^i zk=i=0n1yi×(wnk)i
  = ∑ i = 0 n − 1 ∑ j = 0 n − 1 a j × ( w n i ) j × ( w n − k ) i \quad\ =\sum\limits_{i=0}^{n-1}\sum\limits_{j=0}^{n-1}a_j\times (w_n^i)^j\times(w_n^{-k})^i  =i=0n1j=0n1aj×(wni)j×(wnk)i
  = ∑ j = 0 n − 1 a j ∑ i = 0 n − 1 ( w n j − k ) i \quad\ =\sum\limits_{j=0}^{n-1} a_j\sum\limits_{i=0}^{n-1}(w_n^{j-k})^i  =j=0n1aji=0n1(wnjk)i
∑ i = 0 n − 1 ( w n j − k ) i = { j = k n j ≠ k ( w n j − k ) n − 1 w n j − k − 1 = 0 \sum\limits_{i=0}^{n-1}(w_n^{j-k})^i=\begin{cases}j=k\quad n\\j\ne k\quad \dfrac{(w_n^{j-k})^n-1}{w_n^{j-k}-1}=0\end{cases} i=0n1(wnjk)i= j=knj=kwnjk1(wnjk)n1=0

故当且仅当 j = k j=k j=k z k = a j × n z_k=a_j\times n zk=aj×n

所以对 g ( x ) g(x) g(x) 代入 w n 0 , w n − 1 , ⋯   , w n − ( n − 1 ) w_n^0,w_n^{-1},\cdots,w_n^{-(n-1)} wn0,wn1,,wn(n1) 之后求 DFT 的结果除以 n n n 就可以得到 f ( x ) f(x) f(x) 的各项系数,而形如 w n − 1 w_n^{-1} wn1 的就是共轭复数,可以用 complex 所带的函数 c o n j ( ) conj() conj() 求出。

所以学了一大顿之后就又可以写高精乘法了:

#include
#define ll long long
#define ff(i,s,e) for(int i=(s);i<=(e);++i)
using namespace std;
inline int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
const int N=1<<21;
const double pai=acos(-1);
int len1,len2,n=1,m,ans[N],pos[N];
char s[N];
typedef complex<double> com;
com a[N],b[N],omg[N],inv[N];
inline void fft(com *a,com *omg){
	ff(i,0,n-1) if(pos[i]<i) swap(a[pos[i]],a[i]);//注意不要重复交换 
	for(int len=2;len<=n;len<<=1){
		int m=len>>1;
		for(int i=0;i<n;i+=len) ff(j,0,m-1){
			com tmp=a[i+j+m]*omg[n/len*j];
			a[i+j+m]=a[i+j]-tmp,a[i+j]+=tmp;
		}	
	}
} 
signed main(){
	scanf("%s",s),len1=strlen(s);
	ff(i,0,len1-1) a[i].real(s[len1-i-1]-'0');
	scanf("%s",s),len2=strlen(s);
	ff(i,0,len2-1) b[i].real(s[len2-i-1]-'0');
	while(n<len1+len2) n<<=1,++m;
	ff(i,0,n-1){
		omg[i]=com(cos(2*pai*i/n),sin(2*pai*i/n));
		inv[i]=conj(omg[i]);
		int tmp=0;
		ff(j,0,m-1) if(i&(1<<j)) tmp|=(1<<m-j-1);
		pos[i]=tmp;
	}
	fft(a,omg),fft(b,omg);
	ff(i,0,n-1) a[i]*=b[i];
	fft(a,inv);
	ff(i,0,n-1){
		ans[i]+=floor(a[i].real()/n+0.5);//注意四舍五入 
		ans[i+1]+=ans[i]/10,ans[i]%=10;
	}
	while(n>1&&ans[n-1]==0) --n;
	for(int i=n-1;i>=0;--i) putchar(ans[i]+'0');
	putchar('\n');
}

阶与原根

使得 a n ≡ 1 ( m o d m ) a^n\equiv1\pmod{m} an1(modm) 成立的最小正整数 n n n 叫做 a a a m m m 的阶,符号 δ m ( a ) \delta_m(a) δm(a)

一些性质:

  • ∀ a n ≡ 1 ( m o d m ) , δ m ( a ) ∣ n    ⟹    δ m ( a ) ∣ ϕ ( m ) \forall a^n\equiv 1\pmod{m},\delta_m(a)\mid n\implies\delta_m(a)\mid\phi(m) an1(modm),δm(a)nδm(a)ϕ(m)
  • ∀ i , j ∈ [ 1 , δ m ( a ) ] , i ≠ j   a i ≢ a j ( m o d m ) \forall_{i,j\in[1,\delta_m(a)],i\ne j}\ a^i\not\equiv a^j\pmod{m} i,j[1,δm(a)],i=j aiaj(modm)
  • gcd ⁡ ( a , m ) = 1 , δ m ( a k ) = δ m ( a ) gcd ⁡ ( k , δ m ( a ) ) \gcd(a,m)=1,\delta_m(a^k)=\dfrac{\delta_m(a)}{\gcd(k,\delta_m(a))} gcd(a,m)=1,δm(ak)=gcd(k,δm(a))δm(a)

原根

gcd ⁡ ( a , m ) = 1 , δ m ( a ) = ϕ ( m ) \gcd(a,m)=1,\delta_m(a)=\phi(m) gcd(a,m)=1,δm(a)=ϕ(m),则 a a a m m m 的原根。

  • 判定定理: ∀ p ∣ ϕ ( m ) a ϕ ( m ) p ≢ 1 ( m o d m )    ⟺    a \forall_{p\mid \phi(m)} a^{\frac{\phi(m)}{p}}\not\equiv1\pmod{m}\iff a pϕ(m)apϕ(m)1(modm)a m m m 的原根;
  • 存在定理:只有 2 , 4 , p a , 2 p a 2,4,p^a,2p^a 2,4,pa,2pa 才存在原根,其中 p p p 为奇素数;
  • 原根个数:若 m m m 有原根,则其原根个数为 ϕ ( ϕ ( m ) ) \phi(\phi(m)) ϕ(ϕ(m))
  • m m m 的最小原根 g g g 不超过 m 1 4 m^{\frac{1}{4}} m41,所有其它原根均为 g k   ( gcd ⁡ ( k , ϕ ( m ) = 1 ) ) g^k\ (\gcd(k,\phi(m)=1)) gk (gcd(k,ϕ(m)=1))

暂时就会这么多,NTT 不知道哪年能学会,留坑代填()

为什么我的 FFT 常数这么大啊/fn,未来如果有更优的写法会更新代码。

你可能感兴趣的:(算法,学习,python)