多项式全家桶

多项式全家桶

前置知识:FFT,NTT,卷积。

多项式乘法(卷积)

最基础的东西。为了给下文打基础这里附上NTT模板及封装卷积。

namespace NTT{
int t[maxn];
void init(int n){
    for(int i=1;i<n;i++) t[i]=(t[i>>1]>>1)|(i&1?n>>1:0);
}
void NTT(ll *y,int n,int inv){
    for(int i=0;i<n;i++) if(i<t[i]) std::swap(y[t[i]],y[i]);
    for(int len=2;len<=n;len<<=1){
        ll omg=qpow(inv==1?g:gi,(mod-1)/len);
        for(int i=0;i<n;i+=len){
            ll cur=1;
            for(int j=i;j<i+len/2;j++){
                ll e=y[j];ll o=cur*y[j+len/2]%mod;
                y[j]=(e+o)%mod;y[j+len/2]=(e-o+mod)%mod;
                cur=cur*omg%mod;
            }
        }
    }
    if(inv==-1){
        ll div=qpow(n,mod-2);
        for(int i=0;i<n;i++) y[i]=y[i]*div%mod;
    }
}
}

void poly_mul(ll f[],ll g[],int n){//这里先将g复制一份,防止自乘出错
	memset(p::ves,0,sizeof p::ves);n=get(n);
	for(int i=0;i<n;i++) p::ves[i]=g[i];
    NTT::NTT(f,n,1);NTT::NTT(p::ves,n,1);
    for(int i=0;i<n;i++) f[i]=p::ves[i]*f[i]%mod;
    NTT::NTT(f,n,-1);
}

这里用到了一个 get(x) 函数,是取不小于 x 的最小 \(2^k\)

int get(int x){
    if((x&(x-1))==0) return x;
    while((x-1)&x){
        x-=-x&x;
    }
    return x<<1;
}

多项式求逆

设多项式为 \(F\) ,求其逆 \(G\) ,满足 \(F \cdot G\equiv1 \pmod{x^n}\)。用下标表示多项式项数。

若已知

\[F_n\cdot H_n\equiv1 \pmod{x^n} \]

要求 \(G_{2n} \cdot F_{2n}\equiv 1\pmod{x^{2n}}\) 。则有

\[G_{2n} \cdot F_{n} \equiv1\pmod{x^n}\\ H_n-G_{2n}\equiv0\pmod{x^n}\\ (H_n-G_{2n})^2\equiv0\pmod{x^{2n}}\\ H_n^2-2H_nG_{2n}+G_{2n}^2 \equiv0\pmod{x^{2n}}\\ F_{2n}(H_n^2-2H_nG_{2n}+G_{2n}^2)\equiv0\pmod{x^{2n}}\\ F_{2n}H_n^2-2H_n+G_{2n}\equiv 0 \pmod{x^{2n}}\\ G_{2n}\equiv 2H_n-F_{2n}H_n^2 \pmod{x^{2n}} \]

则可以递归求解。

void poly_inv(ll f[],ll g[],int n){
    if(n==1){memset(g,0,sizeof g);g[0]=qpow(f[0],mod-2);return;}
    poly_inv(f,g,n>>1);
    NTT::init(n<<1);
    for(int i=0;i<n;i++) p::x[i]=f[i],p::y[i]=g[i];
    for(int i=n;i<n<<1;i++) p::x[i]=p::y[i]=g[i]=0;
    NTT::NTT(p::x,n<<1,1),NTT::NTT(p::y,n<<1,1);
    for(int i=0;i<n<<1;i++) p::x[i]=p::x[i]*p::y[i]%mod*p::y[i]%mod;
    NTT::NTT(p::x,n<<1,-1);
    for(int i=0;i<n;i++) adjust(g[i]<<=1),adjust(g[i]=g[i]-p::x[i]);
}

多项式求导/不定积分

没任何难度。

\[\frac{d}{dx}x^a=ax^{a-1}\\ \int x^adx=\frac{x^{a+1}}{a+1} \]

void poly_der(ll f[],ll g[],int n){
    for(int i=0;i<n-1;i++) g[i]=(i+1)*f[i+1]%mod;g[n-1]=0;
}

void poly_int(ll f[],ll g[],int n){
    for(int i=1;i<=n;i++) g[i]=qpow(i,mod-2)*f[i-1]%mod;g[0]=0;
}

多项式对数函数

对于多项式 \(F(x)\),求 \(G(x)\equiv \ln F(x) \pmod{x^n}\)

说实话我最早看到这玩意是很吃惊的,这也能整数取模?现在看是我当时比较年轻。

\[G(x)\equiv \ln F(x) \pmod{x^n}\\ G'(x)\equiv \ln'(F(x)) F’(x) \pmod{x^n}\\ G'(x)\equiv \frac{F'(x)}{F(x)} \pmod{x^n}\\ G(x)\equiv \int\frac{F'(x)}{F(x)}dx \pmod{x^n} \]

其中要用到求 \(F\) 的逆。

void poly_ln(ll f[],ll g[],int n){
    n=get(n);
    for(int i=0;i<n<<1;i++) p::A[i]=p::B[i]=g[i]=0;
    poly_der(f,p::A,n);poly_inv(f,p::B,n);
    poly_mul(p::A,p::B,n<<1);
    poly_int(p::A,g,n);
}

多项式牛顿迭代

假如我们已知函数 \(f(x)\),我们对其在 \(x_0\) 处泰勒展开可得到:

\[f(x)=f(x_0)+\frac{f'(x_0)}{1!}(x-x_0)+\frac{f''(x_0)}{2!}(x-x_0)^2+\frac{f^3(x_0)}{3!}(x-x_0)^3+\cdots \]

泰勒公式本质是个构造函数,当构造的函数在 \(x_0\) 的高次导数都等于原函数的高次导数,那么原函数可以用无限次的多项式函数近似表达。

在多项式中,

我们甚至可以在一个多项式处对复合函数展开:

如果有函数 \(g(f(x))\),其中 \(f(x)\) 是多项式,如果我们对多项式 \(f_0(x)\) 展开,有

\[g(f(x))=g(f_0(x))+\frac{g'(f_0(x))}{1!}(f(x)-f(x_0))+\frac{g''(f_0(x))}{2!}(f(x)-f(x_0))^2+\cdots \]

这里不能将 \(x\) 理解为变量,因为 \(x\) 在多项式中没有什么实际意义,这里自变量是 \(f(x)\)\(f_0(x)\) 是常量。所以将泰勒展开用多项式替换,得到上式。

在牛顿迭代中,我们取泰勒展开的前两项,即将多项式近似成一个切线,求这个切线的根,然后再用这个根展开,反复迭代,可以得到一个近似的根。

在多项式中,略有点区别:

假如我们有 \(G(F_0)\equiv 0 \pmod{x^n}\) ,我们要求 \(G(F)\equiv0 \pmod{x^{2n}}\)

我们对 \(G\)\(F_0\) 处泰勒展开,

\[G(F)\equiv G(F_0) +\frac{G'(F_0)}{1!}(F-F_0)+\frac{G''(F_0)}{2!}(F-F_0)^2+\cdots \pmod{x^{2n}} \]

注意到 \(F-F_0 \equiv 0 \pmod{x^n}\),因此 \((F-F_0)^2 \equiv 0 \pmod{x^{2n}}\)。更高次同理,于是得到

\[G(F)\equiv G(F_0) +G'(F_0)(F-F_0) \pmod{x^{2n}}\\ \]

由于 \(G(F)\equiv0 \pmod{x^{2n}}\) ,所以得到

\[G(F_0) +G'(F_0)(F-F_0)\equiv 0 \pmod{x^{2n}}\\ G'(F_0)F\equiv G'(F_0)F_0 -G(F_0) \pmod{x^{2n}}\\ F\equiv F_0 -\frac{G(F_0)}{G'(F_0)} \pmod{x^{2n}} \]

这是多项式牛顿迭代的基本形式。

多项式指数函数

有多项式 \(A(x)\) ,求 \(B(x) \equiv e^{A(x)} \pmod{x^n}\)

\[B(x)\equiv e^{A(x)} \pmod{x^n}\\ \ln B(x)\equiv {A(x)} \pmod{x^n}\\ \ln B(x)-A(x)\equiv 0 \pmod{x^n}\\ \]

这是关于 \(B(x)\) 的方程。用牛顿迭代求解。令 \(G(B(x))=\ln B(x)-A(x)\)

\(G'(B(x))=\frac{1}{B(x)}\)。若已知 \(B_0(x)\) 在递归底层满足式子,则有

\[\begin{aligned} B(x)&\equiv B_0(x)-\frac{G(B_0(x))}{\frac{1}{B_0(x)}}\\ &\equiv B_0(x)-\frac{\ln B_0(x)-A(x)}{\frac{1}{B_0(x)}}\\ &\equiv B_0(x)-B_0(x)\ln B_0(x)+A(x)B_0(x) \end{aligned} \]

可得

\[B(x) \equiv B_0(x)(1-\ln B_0(x)+A(x)) \]

void poly_exp(ll f[],ll g[],int n){
    if(n==1) return (void)(g[0]=1);
    poly_exp(f,g,n>>1);poly_ln(g,p::z,n);
    NTT::init(n<<1);
    adjust(p::z[0]=f[0]-p::z[0]+1);
    for(int i=1;i<n;i++) adjust(p::z[i]=f[i]-p::z[i]);
    for(int i=n;i<(n<<1);i++) p::z[i]=g[i]=0;
    NTT::NTT(p::z,n<<1,1),NTT::NTT(g,n<<1,1);
    for(int i=0;i<n<<1;i++) g[i]=g[i]*p::z[i]%mod;
    NTT::NTT(g,n<<1,-1);for(int i=n;i<n<<1;i++) g[i]=0;
}

多项式快速幂

给定多项式 \(A(x)\),求 \(B(x)\equiv A^k(x) \pmod{x^n}\)

\[B(x)\equiv e^{k\ln A(x)} \pmod{x^n} \]

直接套板子。

void poly_pow(ll f[],ll g[],int n,ll k){
    for(int i=0;i<n;i++) p::C[i]=0;
    poly_ln(f,p::C,n);
    for(int i=0;i<n;i++) p::C[i]=p::C[i]*k%mod;
    poly_exp(p::C,g,n);
}

多项式平方根

给定多项式 \(A(x)\),求 \(B^2(x)\equiv A(x) \pmod{x^n}\)

也就是我们要求方程 \(G(B(x))=B^2(x)-A(x)\equiv 0 \pmod{x^n}\) 的解。这里只求常数项较小的根。

根据牛顿迭代,有

\[F\equiv F_0 -\frac{G(F_0)}{G'(F_0)} \pmod{x^{2n}} \]

因为 \(G'(B(x))=2B(x)\) ,可得

\(B(x)\equiv B_0(x) -\frac{B_0^2(x)-A(x)}{2B_0(x)}\equiv \frac{B_0^2(x)+A(x)}{2B_0(x)} \pmod{x^{2n}}\)

const ll bdiv=qpow(2,mod-2);

void poly_sqrt(ll f[],ll g[],int n){
    if(n==1) return (void)(g[0]=1);
    poly_sqrt(f,g,n>>1);NTT::init(n<<1);
    poly_inv(g,p::A,n);
    for(int i=0;i<n;i++) p::A[i]=p::A[i]*bdiv%mod;
    for(int i=0;i<n;i++) p::B[i]=f[i];
    for(int i=n;i<n<<1;i++) p::A[i]=p::B[i]=g[i]=0;
    NTT::NTT(p::A,n<<1,1),NTT::NTT(p::B,n<<1,1),NTT::NTT(g,n<<1,1);
    for(int i=0;i<n<<1;i++) g[i]=(g[i]*g[i]+p::B[i])%mod*p::A[i];
    NTT::NTT(g,n<<1,-1);for(int i=n;i<n<<1;i++) g[i]=0;
}
posted @ 2022-05-15 23:19  HyperSQ  阅读(34)  评论(0)    收藏  举报