【线性代数】多项式各类算法总结

前言:

这可能是本蒟蒻在2018省选以前写的最难的东西了。
考虑到时间紧迫,所以有些东西我自己也不能完全理解,只能照搬课件。
只能以后再看机会填坑了

再说回多项式,几乎所有的多项式算法都是基于NTT的优化,也就是说,要想办法把所有的运算,转化为可以使用NTT优化的表达方式。

首先给出几个在之后的证明中可能用到的概念:
一个多项式 A A A的最高次幂,称为该多项式的度,记为 d e g A degA degA

多项式求逆

用数学语言表示,即对于一个多项式 A ( x ) A(x) A(x)
要找出一个对应的 B ( x ) B(x) B(x)满足 ( d e g B < d e g A ) (degB < degA) (degB<degA) A ( x ) B ( x ) ≡ 1 ( m o d ( x n ) ) A(x)B(x)\equiv 1(mod(x^n)) A(x)B(x)1(mod(xn))

用倍增的思路考虑这个问题,假设我们已经得到了一个 B ′ ( x ) B'(x) B(x)使得 A ( x ) B ′ ( x ) ≡ 1 ( m o d ( x ⌊ n 2 ⌋ ) ) A(x)B'(x)\equiv 1(mod(x^{\lfloor \frac n 2\rfloor })) A(x)B(x)1(mod(x2n))
现在希望通过 B ′ ( x ) B'(x) B(x) A ( x ) A(x) A(x)来表示出 B ( x ) B(x) B(x)
首先,很显然 A ( x ) B ( x ) ≡ 1 ( m o d ( x ⌊ n 2 ⌋ ) ) A(x)B(x) \equiv 1(mod(x^{\lfloor \frac n 2\rfloor })) A(x)B(x)1(mod(x2n))
所以很显然 B ( x ) − B ′ ( x ) ≡ 0 ( m o d ( x ⌊ n 2 ⌋ ) ) B(x)-B'(x)\equiv 0(mod(x^{\lfloor \frac n 2\rfloor })) B(x)B(x)0(mod(x2n))
两边同时平方,得到 B 2 ( x ) + B ′ 2 ( x ) − 2 B ( x ) B ′ ( x ) ≡ 0 ( m o d ( x n ) ) B^2(x)+B'^2(x)-2B(x)B'(x)\equiv 0 (mod(x^n)) B2(x)+B2(x)2B(x)B(x)0(mod(xn))

最神奇的一点就是,为什么平方过后,模数也可以跟着平方了:
由于原来左边的多项式在0到 n − 1 n-1 n1项的系数均为0,平方过后,每个在0到 2 ∗ n − 1 2*n-1 2n1之间的项,必有一个因子来源于 0 0 0 n − 1 n-1 n1,则说明平方过后,在0到 2 ∗ n − 1 2*n-1 2n1之间的项的系数仍然为0,所以在模 x n x^n xn下也为0。

代码:

void inv(int n,int *A,int *B){
    if(n==1){
        B[0]=fsp(A[0],MOD-2);
        return ;
    }
    inv((n+1)>>1,A,B);
    static int X[MAXN];
    int p=1;
    for(;p<(n<<1);p<<=1);
    for(int i=0;i<n;i++)
        X[i]=A[i];
    for(int i=n;i<p;i++)
        X[i]=0;
    ntt(X,p,1);
    for(int i=(n+1)>>1;i<p;i++)
        B[i]=0;
    ntt(B,p,1);
    for(int i=0;i!=p;i++)
        B[i]=(ll)B[i]*(2+MOD-(ll)X[i]*B[i]%MOD)%MOD;
    ntt(B,p,-1);
}

多项式除法及取模

给出多项式 A ( x ) 、 B ( x ) A(x)、B(x) A(x)B(x),求出多项式 D ( x ) 、 R ( x ) D(x)、R(x) D(x)R(x)
满足 A ( x ) = D ( x ) B ( x ) + R ( x ) A(x)=D(x)B(x)+R(x) A(x)=D(x)B(x)+R(x)
n = d e g A , m = d e g B n=degA,m=degB n=degA,m=degB
并且 d e g D ≤ d e g A − d e g B = n − m 、 d e g R ≤ m degD\leq degA-degB=n-m、degR\leq m degDdegAdegB=nmdegRm
定义一个 A R ( x ) = x n A ( 1 x ) A^R(x)=x^nA(\frac 1 x) AR(x)=xnA(x1)
A R ( x ) 为 A ( x ) A^R(x)为A(x) AR(x)A(x)将系数按次数反转之后得到的多项式
现在将原式中x替换为 1 x \frac 1 x x1,得到一个新的式子,并在左右同乘以 x n x^n xn
x n A ( 1 x ) = x n − m D ( 1 x ) x m B ( 1 x ) + x n − m + 1 x m − 1 R ( 1 x ) x^nA(\frac 1 x)=x^{n-m}D(\frac 1 x)x^mB(\frac 1 x)+x^{n-m+1}x^{m-1}R(\frac 1 x) xnA(x1)=xnmD(x1)xmB(x1)+xnm+1xm1R(x1)
进而得到
A R ( x ) = D R ( x ) B R ( x ) + x n − m + 1 R R ( x ) A^R(x)=D^R(x)B^R(x)+x^{n-m+1}R^R(x) AR(x)=DR(x)BR(x)+xnm+1RR(x)
其实就是将原式转换为用 X R ( x ) X^R(x) XR(x)来表示

这样一来,可以使用老套路:更换模数(其实上面的求逆本质上也是这个)

发现: D R ( x ) D^R(x) DR(x)的最高次项不高于 x n − m x^{n-m} xnm,但是很显然 x n − m + 1 R R ( x ) x^{n-m+1}R^R(x) xnm+1RR(x)的最低次项都高于了 x n − m x^{n-m} xnm,那么在模 x n − m + 1 x^{n-m+1} xnm+1次项的意义下,就可以消除 R R ( x ) R^R(x) RR(x)带来的影响。

所以, A R ( x ) ≡ B R ( x ) D R ( x ) ( m o d ( x n − m + 1 ) ) A^R(x)\equiv B^R(x)D^R(x) (mod(x^{n-m+1})) AR(x)BR(x)DR(x)(mod(xnm+1))
这样一来,就很好求出 D R ( x ) D^R(x) DR(x)了(通过上面讲的求逆实现),将 D ( x ) D(x) D(x)求出后,再代入 A ( x ) = B ( x ) D ( x ) + R ( x ) A(x)=B(x)D(x)+R(x) A(x)=B(x)D(x)+R(x),就可以求出 R ( x ) R(x) R(x)
代码(这里是取模的代码,求商只需要前半部分即可):


void PolyMod(ll A[],ll B[],int N,int M){
    static ll ta[MAXN],tb[MAXN],tmp[MAXN];
    for(int i=0;i<N;i++) ta[i]=A[N-i-1];
    for(int i=0;i<M;i++) tb[i]=B[M-i-1];
    inv(tb,tmp,M);
    for(int i=0;i<N-M+1;i++) tb[i]=tmp[i];
    for(int i=0;i<4*M;i++) tmp[i]=0;
    mul(ta,tb,N,N-M+1,tmp);
    reverse(tmp,tmp+N-M+1);
    mul(B,tmp,M,N-M+1,tmp);
    for(int i=0;i<M-1;i++)
        A[i]=(A[i]-tmp[i]+MOD)%MOD;
    for(int i=0;i<4*(N+1);i++)
        tmp[i]=0;
}

多项式多点求值与快速插值

多点求值

多点求值,即对多个点同时求其在多项式中的值,即对于一个数 x x x求出 A ( x ) A(x) A(x)的值,并且有多个数同时求值。

这个问题需要二分的思想。
将当前的多项式系数分为两个部分
X [ 0 ] = x 0 , x 1 , x 2 , … … x n 2 X^{[0]}={x_0,x_1,x_2,……x_{\frac n 2}} X[0]=x0,x1,x2,x2n
X [ 1 ] = x n 2 + 1 , x n 2 + 2 , … … , x n − 1 X^{[1]}={x_{\frac n 2+1},x_{\frac n 2+2},……,x_{n-1}} X[1]=x2n+1,x2n+2,,xn1
A [ 0 ] A^{[0]} A[0]表示各点在 X [ 0 ] X^{[0]} X[0]上的取值,用 A [ 1 ] A^{[1]} A[1]表示各点在 X [ 1 ] X^{[1]} X[1]上的取值。
即,我们将原来的多项式按次数大小分成了两个部分,这时如果能够快速求出 A [ 0 ] A^{[0]} A[0] A [ 1 ] A^{[1]} A[1],并且能够在较短时间内合并这两个值,就能得到所有点在当前多项式上的值。

再设两个多项式为:
P [ 0 ] ( x ) = ∏ i = 0 n 2 ( x − x i ) P^{[0]}(x)=\prod_{i=0}^{\frac n 2}(x-x_i) P[0](x)=i=02n(xxi)
P [ 1 ] ( x ) = ∏ i = n 2 + 1 n ( x − x i ) P^{[1]}(x)=\prod_{i=\frac n 2+1}^{n}(x-x_i) P[1](x)=i=2n+1n(xxi)
显然,当 x ∈ X [ 0 ] x\in X^{[0]} xX[0]时, P [ 0 ] ( x ) = 0 P^{[0]}(x)=0 P[0](x)=0,当 x ∈ X [ 1 ] x\in X^{[1]} xX[1]时, P [ 1 ] ( x ) = 0 P^{[1]}(x)=0 P[1](x)=0

将A(x)分别除以 P [ 0 ] ( x ) P^{[0]}(x) P[0](x)
A ( x ) = D ( x ) P [ 0 ] ( x ) + A [ 0 ] ( x ) A(x)=D(x)P^{[0]}(x)+A^{[0]}(x) A(x)=D(x)P[0](x)+A[0](x)
至于为什么可以这么写,原因在于当 x ∈ X [ 0 ] x\in X^{[0]} xX[0]时, P [ 0 ] P^{[0]} P[0]为0
此时 A ( x ) = A [ 0 ] ( x ) A(x)=A^{[0]}(x) A(x)=A[0](x)
也就是说, A ( x ) ≡ A [ 0 ] ( m o d ( P [ 0 ] ( x ) ) ) A(x)\equiv A^{[0]} (mod(P^{[0]}(x))) A(x)A[0](mod(P[0](x)))
这里再运用上面的多项式取模,就可以得到所有点对于这部分的值。再同理求出另一部分即可。
板题与代码链接

快速插值

插值是指:对于一些下标,对应一些取值,求一个多项式满足这些取值
对于这部分,首先应去了解一下拉格朗日插值法。复杂度为 O ( n 2 ) O(n^2) O(n2)

现在考虑优化
同样地,考虑 X [ 0 ] = x i , y i ( 0 ≤ i ≤ n 2 ) X^{[0]}={x_i,y_i}(0\leq i \leq \frac n 2) X[0]=xi,yi(0i2n)
X [ 2 ] = x i , y i ( n 2 + 1 ≤ i ≤ n ) X^{[2]}={x_i,y_i}(\frac n 2+1\leq i \leq n) X[2]=xi,yi(2n+1in)

同样设 A [ 0 ] ( x ) A^{[0]}(x) A[0](x)表示满足 X [ 0 ] X^{[0]} X[0]的多项式
A [ 1 ] ( x ) A^{[1]}(x) A[1](x)表示满足 X [ 1 ] X^{[1]} X[1]的多项式

P ( x ) = ∏ i = 0 i ≤ n 2 ( x − x i ) P(x)=\prod_{i=0}^{i\leq \frac n 2}(x-x_i) P(x)=i=0i2n(xxi)

现在再来构造 A ( x ) A(x) A(x),使其满足
A ( x ) = A [ 1 ] ( x ) P ( x ) + A [ 0 ] ( x ) A(x)=A^{[1]}(x)P(x)+A^{[0]}(x) A(x)=A[1](x)P(x)+A[0](x)
然而我们并不知道 A [ 1 ] ( x ) A^{[1]}(x) A[1](x)
但很容易发现一点,所有在 X [ 0 ] X^{[0]} X[0]中的插值在 P ( x ) P(x) P(x)中均为0,也就是说,要满足这部分值,就必须在 A [ 0 ] A^{[0]} A[0]中满足。
现在只需要使得所有不在 X [ 0 ] X^{[0]} X[0]中的值也能满足,
即对于这部分值,只需要满足 y i = A [ 1 ] ( x i ) P ( x i ) + A [ 0 ] y_i=A^{[1]}(x_i)P(x_i)+A^{[0]} yi=A[1](xi)P(xi)+A[0]

所以可以通过这部分值,先把 A [ 1 ] A^{[1]} A[1]求出来再合并即可。
O ( N l o g 3 N ) O(Nlog^3N) O(Nlog3N)算法代码(即上述方法)
O ( N l o g 2 N ) O(Nlog^2N) O(Nlog2N)算法代码

牛顿迭代法

牛顿迭代法是给出 G ( x ) G(x) G(x),求满足 G ( F ( x ) ) ≡ 0 ( m o d ( x n ) ) G(F(x))\equiv 0(mod (x^n)) G(F(x))0(mod(xn)) F ( x ) F(x) F(x)

还是分治的思路,合并时用到了泰勒展开公式。
假设我们已经求出 F 0 ( x ) F_0(x) F0(x)满足 G ( F 0 ( x ) ) ≡ 0 ( m o d ( x n 2 ) ) G(F_0(x))\equiv 0(mod(x^{\frac n 2})) G(F0(x))0(mod(x2n))
现在要求 F ( x ) F(x) F(x),对 G ( F ( x ) ) G(F(x)) G(F(x)) F 0 ( x ) F_0(x) F0(x)上泰勒展开,得到

G ( F ( x ) ) = G ( F 0 ( x ) ) + G ′ ( F 0 ( x ) ) 1 ! ( F ( x ) − F 0 ( x ) ) 1 + G ′ ′ ( F 0 ( x ) ) 2 ! ( F ( x ) − F 0 ( x ) ) 2 … … G(F(x))=G(F_0(x))+\frac {G'(F_0(x))} {1!}(F(x)-F_0(x))^1+\frac {G''(F_0(x))} {2!}(F(x)-F_0(x))^2…… G(F(x))=G(F0(x))+1!G(F0(x))(F(x)F0(x))1+2!G(F0(x))(F(x)F0(x))2

由于泰勒展开公式是无限长的,所以这里要考虑优化。
由于 d e g ( F ( x ) − F 0 ( x ) ) 2 ≥ n deg(F(x)-F_0(x))^2\geq n deg(F(x)F0(x))2n所以在第三项及以后,在模 x n x^n xn意义下均为0,可以忽略。
所以 G ( F ( x ) ) = G ( F 0 ( x ) ) + G ′ ( F 0 ( x ) ) ( F ( x ) − F 0 ( x ) ) ( m o d ( x n ) ) G(F(x))=G(F_0(x))+{G'(F_0(x))}(F(x)-F_0(x))(mod(x^n)) G(F(x))=G(F0(x))+G(F0(x))(F(x)F0(x))(mod(xn))
再根据定义消掉 G ( F ( x ) ) G(F(x)) G(F(x))
得到 F ( x ) ≡ F 0 ( x ) − G ( F 0 ( x ) ) G ′ ( F 0 ( x ) ) F(x)\equiv F_0(x)-\frac {G(F_0(x))} {G'(F_0(x))} F(x)F0(x)G(F0(x))G(F0(x))

牛顿迭代法的具体应用:多项式开根

为什么说牛顿迭代法是很强的东西呢,因为用这个方法,可以极快速地得到一些结论,比如多项式开根:
给定 P ( x ) P(x) P(x),求 F ( x ) F(x) F(x)满足 F ( x ) 2 − P ( x ) ≡ 0 ( m o d ( x n ) ) F(x)^2-P(x)\equiv 0(mod(x^n)) F(x)2P(x)0(mod(xn))

我们可以将 F ( x ) F(x) F(x)视为这个函数的变量,然后根据牛顿迭代法得到的结论:
F ( x ) = F 0 ( x ) − F 0 ( x ) 2 − P ( x ) 2 ∗ F 0 ( x ) F(x)=F_0(x)-\frac {F_0(x)^2-P(x)} {2*F_0(x)} F(x)=F0(x)2F0(x)F0(x)2P(x)
这里稍微讲一讲:
如果令牛顿迭代法中的 G ( F ( x ) ) = F ( x ) 2 − P ( x ) G(F(x))=F(x)^2-P(x) G(F(x))=F(x)2P(x),那么就能够代入了,原公式为 F ( x ) = F 0 ( x ) − G ( F 0 ( x ) ) G ′ ( F 0 ( x ) ) F(x)=F_0(x)-\frac {G(F_0(x))} {G'(F_0(x))} F(x)=F0(x)G(F0(x))G(F0(x))
G ′ ( x ) G'(x) G(x) G ( x ) G(x) G(x)的导数,当 G ( x ) = F 0 ( x ) 2 − P ( x ) G(x)=F_0(x)^2-P(x) G(x)=F0(x)2P(x)时, G ′ ( x ) = 2 ∗ F 0 ( x ) G'(x)=2*F_0(x) G(x)=2F0(x)(dza大佬 ORZ)
把这些全部代入原式,就能得到该公式。

void Sqrt(int *a,int *b,int len){
    if(len==1){
        b[0]=a[0];//?
        return ;
    }
    Sqrt(a,b,len>>1);
    static int c[MAXN],d[MAXN];
    for(int i=0;i<=len;i++)
        c[i]=a[i];
    Inv(b,d,len);
    ntt(c,len<<1,1);
    ntt(d,len<<1,1);
    for(int i=0;i<(len<<1);i++)
        d[i]=1ll*d[i]*c[i]%MOD;
    ntt(d,len<<1,-1);
    for(int i=0;i<len;i++)
        b[i]=1ll*(1ll*d[i]+b[i])*inv2%MOD;
    for(int i=0;i<=(len<<1);i++)
        c[i]=d[i]=0;

}

同样的方法还可以推多项式求逆

其他多项式算法(补)

多项式求对数
多项式求指数

结语

多项式算法的一些练习题
多项式求逆:BZOJ3456其实后面的题,基本都用到了多项式求逆
多项式取模:BZOJ4944(NOI2017 D1T3),这道题中多项式取模感觉有点鸡肋,因为不用多项式取模照样能暴力过。但如果当 k ≥ 20000 k\geq 20000 k20000时,就只能用多项式取模了。
多项式(除法?)取模:CodeChef RNG,这道题是上一题的简化版,不过必须使用多项式取模。
多项式开根:CF438E The Child And Binary Tree有点分析过程,如果不想分析只想练板可以直接查题解。
另外,很多算法在洛谷中有模板题,建议都打一发。

你可能感兴趣的:(总结,多项式,NTT)