• 多项式总结


    Part1:FFT(fast fast tle)

    LuoguP3803 【模板】多项式乘法(FFT)

    前置知识:复数,单位根,多项式的系数表达法,多项式的点值表达法

    • 复数:

    可以表示为((a+bi)),可以看做原点到((a,b))一个向量,其中(i=sqrt{-1})
    复数可以进行加,减,乘(向量的除法有点问题),其中

    [(a+bi)+(c+di)=((a+c)+(b+d)i) ]

    [(a+bi)-(c+di)=((a-c)+(b-d)i) ]

    [(a+bi)(c+di)=a(c+di)+bi(c+di)=ac+adi+bci+bdi^2=((ac-bd)+(ad+bc)i) ]

    即:

    [(a+bi)(c+di)=((ac-bd)+(ad+bc)i) ]

    同时复数的乘还有和向量一样的几何意义:模长相乘,幅角相加

    • 单位根:

    在OI中,经常用到2的正整数次幂相关的数,因为这样方便处理,为方便,我们规定下文的(n)为2的正整数次幂。
    定义:如果(w_n^n=1)那么(w_n)(n)次单位根
    因为(w_n^n=1),根据复数乘的几何意义,可知模长为1,幅角为(frac{2pi}{n}),易得单位根

    [w_n=(cos(frac{2pi}{n})+sin(frac{2pi}{n})i) ]

    然后(w^k_n)的幅角为(frac{2kpi}{n}),所以

    [w^k_n=(cos(frac{2kpi}{n})+sin(frac{2kpi}{n})i) ]

    单位根这里还需要两个性质:
    性质一:

    [w^{2k}_{2n}=(cos(frac{4kpi}{2n})+sin(frac{4kpi}{2n})i)=(cos(frac{2kpi}{n})+sin(frac{2kpi}{n})i)=w^k_n ]

    [w^{2k}_{2n}=w^k_n ]

    性质二:

    [w^{k+frac{n}{2}}_n=w^kw^{frac{n}{2}}=w^k(cos(frac{npi}{n})+sin(frac{npi}{n})i)=-w^k ]

    [w^{k+frac{n}{2}}=-w^k ]

    • 多项式的系数表达法:

    就是平时的表达方法,用(n+1)个系数表示一个(n)次多项式,比如:

    [f(x)=a_0+a_1x+a_2x^2+a_3x^3+...+a_nx^n ]

    该方法易读,也易求值,但很难快速求卷积。

    • 多项式的点值表达法:

    就是用(n+1)个点来表示一个(n)次多项式,比如:

    [f(x)={(x_0,y_0),(x_1,y_1),(x_2,y_2),...,(x_n,y_n)} ]

    该方法不易理解,但很容易求卷积
    如果两个多项式(f,g)满足(fx_0==gx_0,fx_1==gx_1,...,fx_n==gx_n),则新多项式

    [h(x)={(x_0,fx_0 gx_0),...,(x_n,fx_n gx_n)} ]

    正题:快速傅里叶变换(FFT)以及快速傅里叶逆变换(IFFT)

    从上面两种多项式的表达方式中,我们可以发现如果能快速的把多项式在系数与点值中转换,就可以快速的获取两个多项式的卷积。

    • 1、系数多项式转点值多项式(快速傅里叶变换)

    给出多项式

    [f(x)=a_0+a_1x+a_2x^2+...+a_nx^n ]

    我们需要快速求出(f(1),f(w_n),...,f(w^{n-1}_{n-1}))
    先将(f)按奇偶分类分为

    [f(x)=(a_0+a_2x^2+...+a_nx^n)+(a_1x+a_3x^3+...+a_{n-1}x^{n-1}) ]

    我们设

    [f1(x)=a_0+a_2x+...+a_nx^{frac{n}{2}} ]

    [f2(x)=a_1+a_3x+...+a_{n-1}x^{frac{n-2}{2}} ]

    那么有

    [f(x)=f1(x^2)+xf2(x^2) ]

    带入(x=w^k_n),

    [f(w^k_n)=f1(w^{2k}_n)+w^k_nf2(w^{2k}_n) ]

    带入(x=w^{k+frac{n}{2}}_n)

    [f(w^{k+frac{n}{2}}_n)=f1(w^{2k+n}_n)+w^{k+frac{n}{2}}_nf2(w^{2k+n}_n) ]

    根据性质二,有

    [f(w^{k+frac{n}{2}}_n)=f1(w^{2k}_n)-w^k_nf2(w^{2k}_n) ]

    可以发现(f(w^k_n))(f(w^{k+frac{n}{2}}_n))两项只差第二项的系数,所以我们可以只用一半的时间就处理整个多项式。
    可以发现,如果我们递归的处理,就可以用(O(nlog n))的复杂度实现FFT。

    • 2、点值多项式转系数多项式(快速傅里叶逆变换)

    我们可以把式子列成一个矩阵(没学过矩阵可以先学再看或跳过推导):
    其中((a_0,a_1,a_2,...,a_{n-1}))为系数表达法,((y_0,y_1,y_2,...,y_{n-1}))为省略(x)的点值表达法。

    [left|egin{matrix}1&1&1&cdots&1\1&w_n&w_n^2&cdots&w_n^{n-1}\1&w_n^2&w_n^4&cdots&w_n^{2(n-1)}\vdots&vdots&vdots&ddots&vdots\1&w_n^{n-1}&w_n^{2(n-1)}&cdots&w_n^{(n-1)(n-1)}end{matrix} ight|left|egin{matrix}a_0\a_1\a_2\vdots\ a_{n-1}end{matrix} ight|=left|egin{matrix}y_0\y_1\y_2\vdots\ y_{n-1}end{matrix} ight|]

    如果我们能快速求出左边这个矩阵的逆矩阵,我们就能快速转换。
    考虑矩阵求逆((O(n^3))完全负优化)
    但我们可以发现原矩阵中所有数之间是有关联的,我们可以考虑转换。

    (V)为原矩阵,(G)为逆矩阵,考虑最终矩阵(E)((i,j))上的值:

    [E(i,j)=sumlimits_{k=0}^{n-1}G(j,k) V(k,i)\ =sumlimits_{k=0}^{n-1}G(j,k)w_n^{ki} ]

    因为(V)(G)互逆,所以(E)是单位矩阵,只有当(i=j)时才会有值1,否则为0。

    [sumlimits_{k=0}^{n-1}G(j,k)w_n^{ki}=[i==j] ]

    我们先证明一个引理:当(k)不是(n)的倍数时

    [sumlimits_{j=0}^{n-1}w_n^{kj}=0 ]

    由等比数列求和得

    [sumlimits_{j=0}^{n-1}w_n^{kj}=frac{w_n^{kn}-1}{1-w_n^k}=frac{1-1}{w_n^k-1} ]

    因为(k)不是(n)的倍数,所以(w_n^k ot=1),即分母不为0,所以该引理成立。

    根据这个引理,可以发现矩阵(G)有一个比较简单的构造方式,即(G(i,j)=w_n^{-ij})
    这时

    [E(i,j)=sumlimits_{k=0}w_n^{-jk}w_n^{ki} ]

    [=sumlimits_{k=0}w_n^{i-j} ]

    (i-j)不为(n)的倍数(不为0时),(E(i,j)=1),但当(i=j)时,已知(E(i,j)=n),跟单位矩阵有点偏差,我们在前面加一个(frac{1}{n})

    好吧,这个推导其实有些牵强,只用把他当做结论记就可以了。

    这样,我们就有:

    [left|egin{matrix}1&1&1&cdots&1\1&w_n^{-1}&w_n^{-2}&cdots&w_n^{1-n}\1&w_n^{-2}&w_n^{-4}&cdots&w_n^{2(1-n)}\vdots&vdots&vdots&ddots&vdots\1&w_n^{1-n}&w_n^{2(1-n)}&cdots&w_n^{(1-n)(1-n)}end{matrix} ight|left|egin{matrix}y_0\y_1\y_2\vdots\ y_{n-1}end{matrix} ight|=left|egin{matrix}na_0\na_1\na_2\vdots\ na_{n-1}end{matrix} ight|]

    这样我们就可以用类似系数转点值的方法转换了,只是这边的单位根要取反,其实在使用起来时就是

    [(a+bi)^{-1}=(a-bi) ]

    非常简单,只用在FFT的基础上略作修改即可。

    最后因为直接乘出来的答案是真实值的(n)倍,所以要除以(n)

    然后我们就可以写出最基本的递归版FFT(虽然C++有自带complex类型,但用起来会比较慢,建议手写一个):

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1000010;
    const double pi=acos(-1);
    int n,m,lg[N<<1];
    struct Complex{
        double x,y;
        Complex(double x=0,double y=0):x(x),y(y){}
    };
    Complex operator+(Complex a,Complex b){return Complex(a.x+b.x,a.y+b.y);}
    Complex operator-(Complex a,Complex b){return Complex(a.x-b.x,a.y-b.y);}
    Complex operator*(Complex a,Complex b){return Complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
    Complex c[N<<2],b[N<<2],t[N<<2],s[50];
    void fft(Complex *f,int l,int len,int op){
        if(!len)return;
        for(int i=l;i<l+(len<<1);++i)t[i]=f[i];
        for(int i=l;i<l+len;++i)f[i]=t[l+((i-l)<<1)],f[i+len]=t[l+((i-l)<<1|1)];
        fft(f,l,len>>1,op);
        fft(f,l+len,len>>1,op);
        Complex tmp=s[lg[len]],buf=Complex(1,0),d;
        tmp.y*=op;
        for(int i=l;i<l+len;++i){
            d=buf*f[i+len];
            t[i]=f[i]+d;
            t[i+len]=f[i]-d;
            buf=buf*tmp;
        }
        for(int i=l;i<l+(len<<1);++i)f[i]=t[i];
    }
    int main(){
        int n,m;
        cin>>n>>m;
        for(int i=2;i<=n+m;++i)lg[i]=lg[i>>1]+1;
        for(int i=0;i<=n;++i)scanf("%lf",&b[i].x);
        for(int i=0;i<=m;++i)scanf("%lf",&c[i].x);
        for(m+=n,n=1;n<=m;n<<=1);
        for(int i=1,j=0;i<=n;i<<=1,++j)s[j]=Complex(cos(pi/i),sin(pi/i));
        fft(b,0,n>>1,1);
        fft(c,0,n>>1,1);
        for(int i=0;i<n;++i)b[i]=b[i]*c[i];
        fft(b,0,n>>1,-1);
        for(int i=0;i<=m;++i)printf("%.0lf ",fabs(b[i].x)/n);
    }
    

    但递归版的着实很慢,这时就要用到神奇的:

    二进制反转

    因为开始的时候我们把所有数按奇偶性分类,所以我们不能直接枚举区间长度然后处理。但正是因为我们按奇偶分类,我们可以直接按二进制分类,然后一个数的真实位置就是该数的下标按二进制反转后的值。

    求真实位置的方法其实很简单:

    for(int i=0;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    

    一个数的二进制反转为这个数除2的反转除2(得到后(l-1)位),如果这个数第一位为1,那么把最后一位加1。
    然后就可以用非递归的写法优化递归的大常数:

    #include<bits/stdc++.h>
    #define eps 1e-6
    using namespace std;
    const int N=4e6+10;
    const double pi=acos(-1);
    struct Complex{
        double x,y;
        Complex(double x=0,double y=0):x(x),y(y){}
    };
    Complex operator+(Complex a,Complex b){return Complex(a.x+b.x,a.y+b.y);}
    Complex operator-(Complex a,Complex b){return Complex(a.x-b.x,a.y-b.y);}
    Complex operator*(Complex a,Complex b){return Complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
    int limit,l;
    int r[N];
    void FFT(Complex*f,int op){
        for(int i=0;i<limit;++i){
            if(i<r[i])swap(f[i],f[r[i]]); 
        }
        for(int mid=2;mid<=limit;mid*=2){
            Complex wn=Complex(cos(2*pi/mid),op*sin(2*pi/mid));
            for(int j=0;j<limit;j+=mid){
                Complex w=Complex(1,0);
                for(int k=j;k<j+mid/2;++k,w=w*wn){
                    Complex x=f[k],y=w*f[k+mid/2];
                    f[k]=x+y;
                    f[k+mid/2]=x-y;
                }
            }
        }
        if(op==-1){
            for(int i=0;i<limit;++i){
                f[i].x/=limit;
            }
        }
    }
    int n,m;
    Complex a[N],b[N]; 
    int main(){
        cin>>n>>m;
        for(int i=0;i<=n;++i)scanf("%lf",&a[i].x);
        for(int i=0;i<=m;++i)scanf("%lf",&b[i].x);
        for(limit=1,l=0;limit<=n+m;limit*=2,l++);
        for(int i=0;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
        FFT(a,1),FFT(b,1);
        for(int i=0;i<limit;++i)a[i]=a[i]*b[i];
        FFT(a,-1);
        for(int i=0;i<=n+m;++i){
        	if(fabs(a[i].x)<eps)printf("0 ");
            else printf("%.0lf ",a[i].x);
        }
    }
    

    例题:[ZJOI2014]力

    Part2:NTT(快速数论变换)

    在FFT的时候,因为会用到大量sin和cos以及double的乘法,会让精度有巨大的损失,所以我们要用一些其他的方法来让精度不损失。这时就要用到快速数论变换。

    前置知识:原根

    • 原根

    原根:是一个数学符号。设(m)是正整数,(a)是整数,若(a)(m)的阶等于(varphi(m)),则称(a)为模(m)的一个原根。

    阶:使(a^nequiv 1(mod p))成立的最小正整数(n)叫做(a)(p)的阶。这里(equiv)指同余符号,代表(a^n)除以(p)的余数跟1除以(p)的余数相等。

    一般情况下模数为998244353,而998244353的原根为3。因为有:

    [3^{varphi(998233453)}equiv 3^{998244352}equiv 1(mod 998244353) ]

    而且对于(1leq i< 998244352),没有(3^iequiv 1(mod 998244353))
    下文中我们默认(p=998244353,G=3)(p)为模数,(G)为原根)

    然后我们要尝试用原根相关的东西替换掉单位根:

    我们需要知道(w_n)怎么求:
    因为(w_n^nequiv 1(mod p))
    所以(w_n^nequiv G^{p-1}(mod p))
    所以有(w_nequiv G^{frac{p-1}{n}}(mod p))

    先证明几个性质,

    性质一:(w^{k+frac{n}{2}}=-w^k)
    性质二:(w^{2k}_{2n}=w^k_n)
    这两个性质的证明方法和FFT一致

    所以就跟FFT完全一样了

    #include<bits/stdc++.h>
    using namespace std;
    const int N=3e6+10,p=998244353,G=3,Gi=332748118;
    int add(int x,int y){return(x+y)%p;}
    int mul(int x,int y){return 1ll*x*y%p;}
    int mpow(int a,int n){
        int ret=1;
        while(n){
            if(n&1)ret=mul(ret,a);
            a=mul(a,a);
            n/=2;
    	}
        return ret;
    }
    int n,m;
    int a[N],b[N];
    int r[N],limit;
    void ntt(int*f,int op){
        for(int i=0;i<limit;++i)if(i<r[i])swap(f[i],f[r[i]]);
        for(int len=2;len<=limit;len*=2){
            int wn=mpow(op==1?G:Gi,(p-1)/len);
            for(int j=0;j<limit;j+=len){
                int w=1;
                for(int k=j;k<j+len/2;++k,w=mul(w,wn)){
                    int x=f[k],y=mul(w,f[k+len/2]);
                    f[k]=add(x,y);
                    f[k+len/2]=add(x,p-y);
    			}
    		}
    	}
    	if(op==-1){
    	    int inv=mpow(limit,p-2);
    	    for(int i=0;i<limit;++i){
    	        f[i]=mul(f[i],inv);
    		}
    	}
    }
    int main(){
    	cin>>n>>m;
    	for(int i=0;i<=n;++i)scanf("%d",a+i);
    	for(int i=0;i<=m;++i)scanf("%d",b+i);
    	int l=0;limit=1;
    	while(limit<=n+m)limit*=2,l++;
    	for(int i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    	ntt(a,1);
    	ntt(b,1);
    	for(int i=0;i<limit;++i)a[i]=mul(a[i],b[i]);
    	ntt(a,-1);
    	for(int i=0;i<=n+m;++i)printf("%d ",a[i]);
    }
    

    例题:[AH2017/HNOI2017]礼物

    Part3:多项式求逆

    我们要求

    [A imes Bequiv 1(mod x^n) ]

    现在已经知道了:

    [A imes B'equiv 1(mod x^{frac{n}{2}}) ]

    然后可以转化:

    [A imes (B-B')equiv0(mod x^{frac{n}{2}}) ]

    [B-B'equiv 0(mod x^{frac{n}{2}}) ]

    [(B-B')^2equiv0(mod x^n) ]

    [B^2+B'^2-2BB'equiv0(mod x^n) ]

    [AB^2+AB'^2-2ABB'equiv0(mod x^n) ]

    [B+AB'^2-2B'equiv0(mod x^n) ]

    [Bequiv2B'-AB'^2(mod x^n) ]

    根据这个理论基础,我们可以做出多项式求逆:

    #include<bits/stdc++.h>
    #define res register int
    using namespace std;
    const int N=3e6+10,p=998244353,G=3,Gi=332748118;
    inline int add(res x,res y){return(x+y)>=p?x+y-p:x+y;}
    inline int mul(res x,res y){return 1ll*x*y-1ll*x*y/p*p;}
    inline int mpow(res a,res n){
        res ret=1;
        while(n){
            if(n&1)ret=mul(ret,a);
            a=mul(a,a);
            n/=2;
        }
        return ret;
    }    
    int g[2][N];
    void init(){
        for(int i=1;i<N;i*=2){
            g[0][i]=mpow(G,(p-1)/i);
            g[1][i]=mpow(Gi,(p-1)/i);
        }
    }
    int n,m;
    int ls[5][N];
    int a[N],b[N],c[N];
    int r[N];
    void ntt(int*f,int limit,int op){
        for(res i=0;i<limit;++i)if(i<r[i])swap(f[i],f[r[i]]);
        for(res len=2;len<=limit;len*=2){
            res wn=op==1?g[0][len]:g[1][len];
            for(res j=0;j<limit;j+=len){
                res w=1;
                for(res k=j;k<j+len/2;++k,w=mul(w,wn)){
                    res x=f[k],y=mul(w,f[k+len/2]);
                    f[k]=add(x,y);
                    f[k+len/2]=add(x,p-y);
                }
            }
        }
        if(op==-1){
            res inv=mpow(limit,p-2);
            for(res i=0;i<limit;++i){
                f[i]=mul(f[i],inv);
            }
        }
    }
    void mul(int*a,int*b,int*c,int n,int m){
        int limit=1;
        while(limit<n+m-1)limit*=2;
        for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
        for(int i=0;i<limit;++i)ls[0][i]=a[i],ls[1][i]=b[i];
        ntt(ls[0],limit,1);
        ntt(ls[1],limit,1);
        for(res i=0;i<limit;++i)c[i]=mul(ls[0][i],ls[1][i]);
        ntt(c,limit,-1);
    }
    void inv(int*a,int*b,int n){
    	b[0]=mpow(a[0],p-2);
        for(int len=1,l=0,limit;len<2*n;len*=2){
            limit=len*2,l++;
            for(int i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
            for(int i=0;i<len;++i)ls[0][i]=a[i],ls[1][i]=b[i];
            for(int i=len;i<limit;++i)ls[0][i]=ls[1][i]=0;
            ntt(ls[0],limit,1),ntt(ls[1],limit,1);
            for(int i=0;i<limit;++i){
                b[i]=add(mul(2,ls[1][i]),p-(mul(ls[0][i],mul(ls[1][i],ls[1][i]))));
    		}
    		ntt(b,limit,-1);
    		for(int i=len;i<limit;++i)b[i]=0;
    	}
    }
    inline int read(){
        res ret=0;char c;
        for(c=getchar();!isdigit(c);c=getchar());
        for(;isdigit(c);ret=ret*10+c-'0',c=getchar());
        return ret;
    }
    int main(){
    	init();
        cin>>n;
        for(res i=0;i<n;++i)a[i]=read();
        inv(a,c,n);
        for(res i=0;i<n;++i)printf("%d ",c[i]);puts("");
    }
    

    Part 4:多项式ln

    我们要求

    [A=ln(B) ]

    可推导:

    [A'=(ln(B))' ]

    [A'=frac{1}{B}B' ]

    [A=intfrac{1}{B}B'dx ]

    #include<bits/stdc++.h>
    #define res register int
    using namespace std;
    const int N=3e6+10,p=998244353,G=3,Gi=332748118;
    inline int add(res x,res y){return(x+y)>=p?x+y-p:x+y;}
    inline int mul(res x,res y){return 1ll*x*y-1ll*x*y/p*p;}
    inline int mpow(res a,res n){
        res ret=1;
        while(n){
            if(n&1)ret=mul(ret,a);
            a=mul(a,a);
            n/=2;
        }
        return ret;
    }    
    int g[2][N];
        void init(){
            for(int i=1;i<N;i*=2){
                g[0][i]=mpow(G,(p-1)/i);
                g[1][i]=mpow(Gi,(p-1)/i);
            }
        }
    int n,m;
    int ls[5][N],used;
    //0,1 mul
    //2,3,4 ln
    int a[N],b[N],c[N];
    int r[N];
    void ntt(int*f,int limit,int op){
        for(res i=0;i<limit;++i)if(i<r[i])swap(f[i],f[r[i]]);
        for(res len=2;len<=limit;len*=2){
            res wn=op==1?g[0][len]:g[1][len];
            for(res j=0;j<limit;j+=len){
                res w=1;
                for(res k=j;k<j+len/2;++k,w=mul(w,wn)){
                    res x=f[k],y=mul(w,f[k+len/2]);
                    f[k]=add(x,y);
                    f[k+len/2]=add(x,p-y);
                }
            }
        }
        if(op==-1){
            res inv=mpow(limit,p-2);
            for(res i=0;i<limit;++i){
                f[i]=mul(f[i],inv);
            }
        }
    }
    void mul(int*a,int*b,int*c,int n,int m){
        int limit=1;
        while(limit<n+m-1)limit*=2;
        for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
        for(int i=0;i<limit;++i)ls[0][i]=a[i],ls[1][i]=b[i];
        ntt(ls[0],limit,1);
        ntt(ls[1],limit,1);
        for(res i=0;i<limit;++i)c[i]=mul(ls[0][i],ls[1][i]);
        ntt(c,limit,-1);
    }
    void inv(int*a,int*b,int n){
    	b[0]=mpow(a[0],p-2);
        for(int len=1,l=0,limit;len<2*n;len*=2){
            limit=len*2,l++;
            for(int i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
            for(int i=0;i<len;++i)ls[0][i]=a[i],ls[1][i]=b[i];
            for(int i=len;i<limit;++i)ls[0][i]=ls[1][i]=0;
            ntt(ls[0],limit,1),ntt(ls[1],limit,1);
            for(int i=0;i<limit;++i){
                b[i]=add(mul(2,ls[1][i]),p-(mul(ls[0][i],mul(ls[1][i],ls[1][i]))));
    		}
    		ntt(b,limit,-1);
    		for(int i=len;i<limit;++i)b[i]=0;
    	}
    }
    void direv(int*a,int*b,int n){
        for(int i=1;i<n;++i){
            b[i-1]=mul(a[i],i);
    	}
    }
    void inter(int*a,int*b,int n){
    	b[0]=0;
        for(int i=1;i<n;++i){
            b[i]=mul(a[i-1],mpow(i,p-2));
    	}
    }
    void ln(int*a,int*b,int n){
    	direv(a,ls[2],n);
    	inv(a,ls[3],n);
    	mul(ls[2],ls[3],ls[4],n,n);
    	inter(ls[4],b,2*n);
    	for(int i=n;i<2*n;++i)b[i]=0;
    }
    void sqrt(int*a,int*b,int n){
        b[0]=1;
    }
    inline int read(){
        res ret=0;char c;
        for(c=getchar();!isdigit(c);c=getchar());
        for(;isdigit(c);ret=ret*10+c-'0',c=getchar());
        return ret;
    }
    int main(){
    	init();
        cin>>n;
        for(res i=0;i<n;++i)a[i]=read();
        ln(a,c,n);
        for(res i=0;i<n;++i)printf("%d ",c[i]);puts("");
    }
    

    Part ex:黑科技

    突然学会了一个黑科技:

    牛顿迭代,泰勒展开

    (F,F_0)是多项式,(G)是某个多项式函数。

    现在要求

    [G(F)equiv 0(mod x^n) ]

    现在已经知道了

    [G(F_0)equiv0(mod x^frac n2) ]

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

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

    因为(F)(F_0)的前(frac n2)相同,所以((F-F_0))的前(frac n2)为0,所以对于(n>1)的情况((F-F_0)^n)的前n为必定为0,对答案无意义,可舍去。

    所以有

    [G(F)equiv G(F_0)+G'(F_0)(F-F_0)(mod x^n) ]

    因为(G(F)equiv 0(mod x^n)),所以有

    [0equiv G(F_0)+G'(F_0)(F-F_0)(mod x^n) ]

    [F=frac{-G(F_0)+G'(F_0)F_0}{G'(F_0)}(mod x^n) ]

    [F=F_0-frac{G(F_0)}{G'(F_0)}(mod x^n) ]

    这里要注意当求(G'(F))时,我们要把(F)当成一个未知数,这样(G'(F)=G'F)

    Part 4:多项式exp

    用黑科技可求解。

    给出多项式(A)

    [G(F)=ln(F)-A ]

    [G'(F)=frac1F ]

    [F=F_0-frac{ln(F_0)-A}{frac 1{F_0}} ]

    [F=F_0(1-ln(F_0)+A) ]

    #include<bits/stdc++.h>
    #define res register int
    using namespace std;
    const int N=3e6+10,p=998244353,G=3,Gi=332748118;
    inline int add(res x,res y){return(x+y)>=p?x+y-p:x+y;}
    inline int mul(res x,res y){return 1ll*x*y-1ll*x*y/p*p;}
    inline int mpow(res a,res n){
        res ret=1;
        while(n){
            if(n&1)ret=mul(ret,a);
            a=mul(a,a);
            n/=2;
        }
        return ret;
    }    
    int g[2][N];
    void init(){
        for(int i=1;i<N;i*=2){
            g[0][i]=mpow(G,(p-1)/i);
            g[1][i]=mpow(Gi,(p-1)/i);
        }
    }
    int n,m;
    int ls[10][N],used;
    //0,1 mul
    //2,3,4 ln
    //5,6 exp
    int a[N],b[N],c[N];
    int r[N];
    void ntt(int*f,int limit,int op){
        for(res i=0;i<limit;++i)if(i<r[i])swap(f[i],f[r[i]]);
        for(res len=2;len<=limit;len*=2){
            res wn=op==1?g[0][len]:g[1][len];
            for(res j=0;j<limit;j+=len){
                res w=1;
                for(res k=j;k<j+len/2;++k,w=mul(w,wn)){
                    res x=f[k],y=mul(w,f[k+len/2]);
                    f[k]=add(x,y);
                    f[k+len/2]=add(x,p-y);
                }
            }
        }
        if(op==-1){
            res inv=mpow(limit,p-2);
            for(res i=0;i<limit;++i){
                f[i]=mul(f[i],inv);
            }
        }
    }
    void mul(int*a,int*b,int*c,int n,int m){
        int limit=1;
        while(limit<n+m-1)limit*=2;
        for(int i=0;i<limit;++i)ls[0][i]=ls[1][i]=0;
        for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
        for(int i=0;i<limit;++i)ls[0][i]=a[i],ls[1][i]=b[i];
        ntt(ls[0],limit,1);
        ntt(ls[1],limit,1);
        for(res i=0;i<limit;++i)c[i]=mul(ls[0][i],ls[1][i]);
        ntt(c,limit,-1);
    }
    void inv(int*a,int*b,int n){
    	b[0]=mpow(a[0],p-2);
        for(int len=1,limit;len<2*n;len*=2){
            limit=len*2;
            for(int i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
            for(int i=0;i<len;++i)ls[0][i]=a[i],ls[1][i]=b[i];
            for(int i=len;i<limit;++i)ls[0][i]=ls[1][i]=0;
            ntt(ls[0],limit,1),ntt(ls[1],limit,1);
            for(int i=0;i<limit;++i){
                b[i]=add(mul(2,ls[1][i]),p-(mul(ls[0][i],mul(ls[1][i],ls[1][i]))));
    		}
    		ntt(b,limit,-1);
    		for(int i=len;i<limit;++i)b[i]=0;
    	}
    }
    void direv(int*a,int*b,int n){
        for(int i=1;i<n;++i){
            b[i-1]=mul(a[i],i);
    	}
    }
    void inter(int*a,int*b,int n){
    	b[0]=0;
        for(int i=1;i<n;++i){
            b[i]=mul(a[i-1],mpow(i,p-2));
    	}
    }
    void ln(int*a,int*b,int n){
    	direv(a,ls[2],n);
    	inv(a,ls[3],n);
    	mul(ls[2],ls[3],ls[4],n,n);
    	inter(ls[4],b,2*n);
    	for(int i=n;i<2*n;++i)b[i]=0;
    }
    void exp(int*a,int*b,int n){
    	b[0]=1;
        for(int len=1;len<2*n;len*=2){
            int limit=len*2;
    		ln(b,ls[5],len);
    		for(int i=0;i<len;++i){
    		    ls[5][i]=add(p-ls[5][i],a[i]);
    		}
    		ls[5][0]=add(ls[5][0],1);
    		for(int i=0;i<len;++i)ls[6][i]=b[i];
    		mul(ls[5],ls[6],b,len,len);
    		for(int i=len;i<limit;++i)b[i]=0;
    	}
    }
    inline int read(){
        res ret=0;char c;
        for(c=getchar();!isdigit(c);c=getchar());
        for(;isdigit(c);ret=ret*10+c-'0',c=getchar());
        return ret;
    }
    int main(){
    	init();
        cin>>n;
        for(res i=0;i<n;++i)a[i]=read();
        exp(a,c,n);
        for(res i=0;i<n;++i)printf("%d ",c[i]);puts("");
    }
    

    Part 5:多项式开根

    也使用上文黑科技:

    给出多项式(A)

    [G(F)=F^2-A ]

    [G'(F)=2F ]

    [F=F_0-frac{F_0^2-A}{2F_0} ]

    [F=frac{F_0^2+A}{2F_0} ]

    [F=frac{F_0}2+frac{A}{2F_0} ]

    #include<bits/stdc++.h>
    #define res register int
    #define ll long long
    ll js;
    using namespace std;
    const int N=3e6+10,p=998244353,G=3,Gi=332748118;
    inline int add(res x,res y){return(x+y)%p;}
    inline int mul(res x,res y){return 1ll*x*y%p;}
    inline int mpow(res a,res n){
        res ret=1;
        while(n){
            if(n&1)ret=mul(ret,a);
            a=mul(a,a);
            n/=2;
        }
        return ret;
    }    
    int g[2][N];
    int inv2;
    void init(){
    	inv2=mpow(2,p-2);
        for(int i=1;i<N;i*=2){
            g[0][i]=mpow(G,(p-1)/i);
            g[1][i]=mpow(Gi,(p-1)/i);
        }
    }
    int n,m;
    int ls[7][N],used;
    //0,1 mul inv
    //2,3,4 ln sqrt
    //5,6 exp
    int a[N],b[N],c[N];
    int r[N];
    void ntt(int*f,res limit,res op){
        for(res i=0;i<limit;++i)if(i<r[i])swap(f[i],f[r[i]]);
        for(res len=2;len<=limit;len*=2){
            res wn=op==1?g[0][len]:g[1][len];
            for(res j=0;j<limit;j+=len){
                res w=1;
                for(res k=j;k<j+len/2;++k,w=mul(w,wn)){
                    res x=f[k],y=mul(w,f[k+len/2]);
                    f[k]=add(x,y);
                    f[k+len/2]=add(x,p-y);
                }
            }
        }
        if(op==-1){
            res inv=mpow(limit,p-2);
            for(res i=0;i<limit;++i){
                f[i]=mul(f[i],inv);
            }
        }
    }
    void mul(int*a,int*b,int*c,int n,int m){
        int limit=1;
        while(limit<n+m-1)limit*=2;
        for(res i=0;i<limit;++i)ls[0][i]=ls[1][i]=0;
        for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
        for(res i=0;i<limit;++i)ls[0][i]=a[i],ls[1][i]=b[i];
        ntt(ls[0],limit,1);
        ntt(ls[1],limit,1);
        for(res i=0;i<limit;++i)c[i]=mul(ls[0][i],ls[1][i]);
        ntt(c,limit,-1);
    }
    void inv(int*a,int*b,int n){
    	b[0]=mpow(a[0],p-2);
        for(res len=2,limit;len<2*n;len*=2){
            limit=len*2;
            for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
            for(res i=0;i<len;++i)ls[0][i]=a[i],ls[1][i]=b[i];
            for(res i=len;i<limit;++i)ls[0][i]=ls[1][i]=0;
            ntt(ls[0],limit,1),ntt(ls[1],limit,1);
            for(res i=0;i<limit;++i){
                b[i]=mul(add(2,p-(mul(ls[0][i],ls[1][i]))),ls[1][i]);
    		}
    		ntt(b,limit,-1);
    		for(res i=len;i<limit;++i)b[i]=0;
    	}
    }
    inline void direv(int*a,int*b,int n){
        for(res i=1;i<n;++i){
            b[i-1]=mul(a[i],i);
    	}
    }
    inline void inter(int*a,int*b,int n){
    	b[0]=0;
        for(res i=1;i<n;++i){
            b[i]=mul(a[i-1],mpow(i,p-2));
    	}
    }
    void ln(int*a,int*b,int n){
    	direv(a,ls[2],n);
    	inv(a,ls[3],n);
    	mul(ls[2],ls[3],ls[4],n,n);
    	inter(ls[4],b,2*n);
    	for(res i=n;i<2*n;++i)b[i]=0;
    }
    void exp(int*a,int*b,int n){
    	b[0]=1;
        for(res len=2;len<2*n;len*=2){
            res limit=len*2;
    		ln(b,ls[5],len);
    		for(res i=0;i<len;++i){
    		    ls[5][i]=add(p-ls[5][i],a[i]);
    		}
    		ls[5][0]=add(ls[5][0],1);
    		for(res i=0;i<len;++i)ls[6][i]=b[i];
    		mul(ls[5],ls[6],b,len,len);
    		for(res i=len;i<limit;++i)b[i]=0;
    	}
    }
    void sqrt(int*a,int*b,int n){
    	b[0]=1;
        for(res len=2;len<2*n;len*=2){
            res limit=len*2;
            inv(b,ls[2],len);
            for(res i=0;i<len;++i)ls[3][i]=a[i];
            mul(ls[2],ls[3],ls[4],len,len);
            for(res i=0;i<len;++i)b[i]=mul(add(b[i],ls[4][i]),inv2);
            for(res i=len;i<limit;++i)b[i]=0;
        }
    }
    inline int read(){
        res ret=0;char c;
        for(c=getchar();!isdigit(c);c=getchar());
        for(;isdigit(c);ret=ret*10+c-'0',c=getchar());
        return ret;
    }
    int main(){
    
    	init();
        cin>>n;
        for(res i=0;i<n;++i)a[i]=read();
        sqrt(a,c,n);
        for(res i=0;i<n;++i)printf("%d ",c[i]);puts("");
    
    }
    

    Part 6:多项式快速幂

    直接换底公式即可

    #include<bits/stdc++.h>
    #define res register int
    using namespace std;
    const int N=3e6+10,p=998244353,G=3,Gi=332748118;
    inline int add(res x,res y){return(x+y)%p;}
    inline int mul(res x,res y){return 1ll*x*y%p;}
    inline int mpow(res a,res n){
        res ret=1;
        while(n){
            if(n&1)ret=mul(ret,a);
            a=mul(a,a);
            n/=2;
        }
        return ret;
    }    
    int g[2][N];
    int inv2;
    void init(){
    	inv2=mpow(2,p-2);
        for(int i=1;i<N;i*=2){
            g[0][i]=mpow(G,(p-1)/i);
            g[1][i]=mpow(Gi,(p-1)/i);
        }
    }
    int n,m;
    int ls[7][N],used;
    //0,1 mul inv
    //2,3,4 ln sqrt
    //5,6 exp
    int a[N],b[N],c[N];
    int r[N];
    void ntt(int*f,res limit,res op){
        for(res i=0;i<limit;++i)if(i<r[i])swap(f[i],f[r[i]]);
        for(res len=1;len<=limit;len*=2){
            res wn=op==1?g[0][len]:g[1][len];
            for(res j=0;j<limit;j+=len){
                res w=1;
                for(res k=j;k<j+len/2;++k,w=mul(w,wn)){
                    res x=f[k],y=mul(w,f[k+len/2]);
                    f[k]=add(x,y);
                    f[k+len/2]=add(x,p-y);
                }
            }
        }
        if(op==-1){
            res inv=mpow(limit,p-2);
            for(res i=0;i<limit;++i){
                f[i]=mul(f[i],inv);
            }
        }
    }
    void mul(int*a,int*b,int*c,int n,int m){
        int limit=1;
        while(limit<n+m-1)limit*=2;
        for(res i=0;i<limit;++i)ls[0][i]=ls[1][i]=0;
        for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
        for(res i=0;i<limit;++i)ls[0][i]=a[i],ls[1][i]=b[i];
        ntt(ls[0],limit,1);
        ntt(ls[1],limit,1);
        for(res i=0;i<limit;++i)c[i]=mul(ls[0][i],ls[1][i]);
        ntt(c,limit,-1);
    }
    void inv(int*a,int*b,int n){
    	b[0]=mpow(a[0],p-2);
    	res limit;
        for(res len=1;len<2*n;len*=2){
            limit=len*2;
            for(res i=1;i<limit;++i)r[i]=(r[i>>1]>>1)|((i&1)*limit/2);
            for(res i=0;i<len;++i)ls[0][i]=a[i],ls[1][i]=b[i];
            for(res i=len;i<limit;++i)ls[0][i]=ls[1][i]=0;
            ntt(ls[0],limit,1),ntt(ls[1],limit,1);
            for(res i=0;i<limit;++i){
                b[i]=mul(add(2,p-(mul(ls[0][i],ls[1][i]))),ls[1][i]);
    		}
    		ntt(b,limit,-1);
    		for(res i=len;i<limit;++i)b[i]=0;
    	}
    	for(int i=n;i<limit;++i)b[i]=0;
    }
    inline void direv(int*a,int*b,int n){
        for(res i=1;i<n;++i){
            b[i-1]=mul(a[i],i);
    	}
    	b[n-1]=0;
    }
    inline void inter(int*a,int*b,int n){
    	b[0]=0;
        for(res i=1;i<n;++i){
            b[i]=mul(a[i-1],mpow(i,p-2));
    	}
    }
    void ln(int*a,int*b,int n){
    	direv(a,ls[2],n);
    	inv(a,ls[3],n);
    	mul(ls[2],ls[3],ls[4],n,n);
    	inter(ls[4],b,2*n);
    	for(res i=n;i<2*n;++i)b[i]=0;
    	for(res i=0;i<n;++i)ls[2][i]=ls[3][i]=ls[4][i]=0;
    }
    void exp(int*a,int*b,int n){
    	b[0]=1;
        for(res len=1;len<2*n;len*=2){
            res limit=len*2;
    		ln(b,ls[5],len);
    		for(res i=0;i<len;++i){
    		    ls[5][i]=add(p-ls[5][i],a[i]);
    		}
    		ls[5][0]=add(ls[5][0],1);
    		for(res i=0;i<len;++i)ls[6][i]=b[i];
    		mul(ls[5],ls[6],b,len,len);
    		for(res i=len;i<limit;++i)b[i]=0;
    	}
    }
    void sqrt(int*a,int*b,int n){
    	b[0]=1;
        for(res len=2;len<2*n;len*=2){
            res limit=len*2;
            inv(b,ls[2],len);
            for(res i=0;i<len;++i)ls[3][i]=a[i];
            mul(ls[2],ls[3],ls[4],len,len);
            for(res i=0;i<len;++i)b[i]=mul(add(b[i],ls[4][i]),inv2);
            for(res i=len;i<limit;++i)b[i]=0;
        }
    }
    inline int read(){
        res ret=0;char c;
        for(c=getchar();!isdigit(c);c=getchar());
        for(;isdigit(c);ret=add(mul(ret,10),c-'0'),c=getchar());
        return ret;
    }
    int main(){
    	init();
    	n=read(),m=read();
        for(res i=0;i<n;++i)a[i]=read();
        ln(a,b,n);
        for(int i=0;i<n;++i)b[i]=mul(b[i],m);
    	exp(b,c,n); 
        for(res i=0;i<n;++i)printf("%d ",c[i]);puts("");
    }
    
  • 相关阅读:
    基于RockMq 实现分布式事务
    开机启动脚本编写
    JS 校验笔记
    git 小计
    EasyExcel简单导入示例
    vue的rule中使用validator(异步请求)验证
    字符串、数值、布尔值、函数参数的解构赋值以及圆括号问题
    对象解构赋值
    数组的解构赋值与yield
    window10的cmd重置子系统ubuntu用户密码以及ubuntu设置密码切换用户
  • 原文地址:https://www.cnblogs.com/zhenglier/p/10892720.html
走看看 - 开发者的网上家园