多项式牛顿迭代和多项式开根

orz picks

前置skill

基础求导

(全世界应该只有我不会求导了吧……=。=,但考虑到可能有小学生在看这篇文章,所以还是讲一下吧
导数,是微积分中的重要基础概念,关于它的具体内容就不讲了,在本文中,你只要知道,求导是用来拟合一个函数在各处的斜率的东西,并且有:
f ( x ) = x a ,则 f ( x ) = a x a 1 f ( x ) :一阶导数)。由此可知,还有 f ( x ) = a ( a 1 ) x a 2 f ( x ) :二阶导数)等等。

泰勒展开

将一个函数在 x 0 处泰勒展开可得:

f ( x ) = f ( x 0 ) + f ( x 0 ) 1 ! ( x x 0 ) + f ( x 0 ) 2 ! ( x x 0 ) 2 + . . . + f n ( x 0 ) n ! ( x x 0 ) n

多项式求逆

戳这里

多项式牛顿迭代

已知 G ( x ) ,求解 G ( F ( x ) ) 0 ( mod x n )

假设已经存在 G ( H ( x ) ) 0 ( mod x n 2 )
我们把 G ( F ( x ) ) H ( x ) 处泰勒展开一下。
又因为 G ( H ( x ) ) 0 ( mod x n 2 ) ,而 G ( F ( x ) ) 0 ( mod x n ) G ( F ( x ) ) F ( x ) 次数较小的 n 2 项在模 x n 意义下能够产生的贡献是0,而 G 是一个多项式,所以次数较大的 n 2 项不会影响次数较小的这些项,因此 F ( x ) 的前 n 2 项就是 H ( x ) ,则 F ( x ) H ( x ) 的前 n 2 项为0,则 ( F ( x ) H ( x ) ) 2 ( mod x n ) 0
则有:

G ( F ( x ) ) G ( H ( x ) ) + G ( H ( x ) ) ( F ( x ) H ( x ) )   0 ( mod x n )

也就是:
F ( x ) H ( x ) G ( H ( x ) ) G ( H ( x ) ) ( mod x n )

于是我们可以不断缩小 n 的规模,同时利用多项式求逆来搞这些东西了。

多项式开根

即求 F ( x ) 2 P ( x ) ( mod x n ) P ( x ) 已知,就是一个常数。
那么我们设 G ( A ) = A 2 P ,于是我们就可以利用多项式牛顿迭代的式子来求解 F ( x ) 辣。
很容易知道:

F ( x ) = H ( x ) H ( x ) 2 P ( x ) 2 H ( x )

例题

codeforces 438E/bzoj3625 小朋友和二叉树
我们设 f ( x ) 表示权值和为 x 的二叉树形态个数,设 a ( x ) 表示 c 集合中是否存在权值 x ,则通过枚举根节点权值和根节点两个叉上子树的权值和,可得一个递推式:

f ( t ) = f ( x ) f ( y ) a ( t x y )

边界: f ( 0 ) = 1
也就是说, f ( x ) = f ( x ) 2 a ( x ) + 1
解得: f ( x ) = 1 ± 1 4 a ( x ) 2 a ( x )
分子分母同时乘以 1 1 4 a ( x ) ,并舍去分母有可能为0的那个解可得:
f ( x ) = 2 1 + 1 4 a ( x )

多项式求逆+开根即可完成本题。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
const int mod=998244353,N=262150,G=3;
int n,m,inv2;
int A[N],B[N],C[N],rev[N],c[N],tmp[N],len[N];
int ksm(int x,int y) {
    int re=1;
    for(;y;y>>=1,x=1LL*x*x%mod) if(y&1) re=1LL*re*x%mod;
    return re;
}
void NTT(int *a,int n,int x) {
    for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
    for(RI i=1;i<n;i<<=1) {
        int gn=ksm(G,(mod-1)/(i<<1));
        for(RI j=0;j<n;j+=(i<<1)) {
            int t1,t2,g=1;
            for(RI k=0;k<i;++k,g=1LL*g*gn%mod) {
                t1=a[j+k],t2=1LL*g*a[j+i+k]%mod;
                a[j+k]=(t1+t2)%mod,a[j+i+k]=(t1-t2+mod)%mod;
            }
        }
    }
    if(x==1) return;
    reverse(a+1,a+n);int inv=ksm(n,mod-2);
    for(RI i=0;i<n;++i) a[i]=1LL*a[i]*inv%mod;
}
void getrev(int t)
    {for(RI i=0;i<t;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len[t]-1));}
void getinv(int *a,int *b,int n) {
    if(n==1) {b[0]=ksm(a[0],mod-2);return;}
    getinv(a,b,n>>1);
    int kn=n<<1;getrev(kn);
    for(RI i=0;i<n;++i) c[i]=a[i],c[n+i]=0;
    NTT(c,kn,1),NTT(b,kn,1);
    for(RI i=0;i<kn;++i) b[i]=1LL*b[i]*(2-1LL*c[i]*b[i]%mod+mod)%mod;
    NTT(b,kn,-1);
    for(RI i=n;i<kn;++i) b[i]=0;
}
void getsqrt(int *a,int *b,int n) {
    if(n==1) {b[0]=1;return;}
    getsqrt(a,b,n>>1);int kn=n<<1;
    for(RI i=0;i<n;++i) tmp[i]=0;
    getinv(b,tmp,n),getrev(kn);
    for(RI i=0;i<n;++i) c[i]=a[i],c[n+i]=0;
    NTT(c,kn,1),NTT(b,kn,1),NTT(tmp,kn,1);
    for(RI i=0;i<kn;++i) b[i]=1LL*(b[i]+1LL*c[i]*tmp[i]%mod)%mod*inv2%mod;
    NTT(b,kn,-1);
    for(RI i=n;i<kn;++i) b[i]=0;
}
int main()
{
    n=read(),m=read();
    for(RI i=1;i<=n;++i) A[read()]=1;
    int kn=1;while(kn<=m) kn<<=1,len[kn]=len[kn>>1]+1;
    len[kn<<1]=len[kn]+1;
    for(RI i=0;i<kn;++i) A[i]=(mod-4LL*A[i]%mod)%mod;
    A[0]=1,inv2=ksm(2,mod-2);
    getsqrt(A,B,kn),B[0]=(B[0]+1)%mod;
    getinv(B,C,kn);
    for(RI i=1;i<=m;++i) printf("%d\n",(C[i]+C[i])%mod);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/80888764