zoukankan      html  css  js  c++  java
  • 多项式计算导论

    原创by Sky_miner
    定义部分纯属口胡,有不严谨的地方可以在下面评论。
    所有代码在最后给出

    前置技能

    多项式的表示

    系数表示法

    定义一个多项式(f(x) = sum_{i=0}^na_ix^i)前式之中(x)是一个不定元,不表示任何值
    则我们可以使用({a_1,a_2,...,a_{n-1},a_n})来表示一个多项式的系数。
    其中(n)即最高次项的次数则被称作多项式(f(x))的度数或次数,一般记作(deg_f)

    点值表示法

    定义一个多项式(f(x)),并用多个点对((x_i,y_i))表示,满足对任意的(i ext{都有} f(x_i) = y_i),且这个多项式可以由这些点对唯一确定.
    一般点值表示法只用于加速多项式乘法,也就是我们常说的快速傅立叶变换(Fast Fourier Transform, FFT)

    复数

    复数分为实部和虚部,形式为(a + bi)其中(i^2 = -1).

    复数运算

    加法((a_1+a_2) + (b_1+b_2)i)
    减法((a_1-a_2) - (b_1-b_2)i)
    乘法((a_1+b_1i)*(a_2+b_2i) = (a_1*a_2 - b_1*b_2) + (a_1*b_2 + a_2*b_1)i)
    对实数的运算则使实部和虚部同时对那个数进行运算

    单位根

    (n)次单位根为满足(z^n = 1)的复数,这样的复数共有(n)个且均匀分布在复平面的一个单位圆上。
    由数学知识可得,(n)次单位根为

    [e^{frac{2kπi}{n}},k = 0,1,2,...,n-1 ]

    (e^{θi} = cos θ + isin θ)
    我们记(omega_n = e^{frac{2πi}{n}}),则(n)次单位根为(omega_n^0,omega_n^1,...,omega_n^{n-1})

    多项式的求导及积分

    求导和积分互为逆运算
    求导有:(g_i = f_{i+1}*(i+1))(g_n = 0)
    积分有:(g_i = f_{i-1}/i)(g_0 = 0)
    复杂度均为(O(n))

    正片开始:多项式的运算

    计算多项式的和即(A(x) + B(x))

    即给定多项式(A(x),B(x))

    [A(x) = sum_{i=0}^na_ix^i ]

    [B(x) = sum_{i=0}^nb_ix^i ]

    求一个多项式(C(x))满足

    [C(x) = sum_{i=0}^n(a_i+b_i)x^i ]

    系数对位相加,复杂度(O(n));

    计算多项式的差即(A(x) - B(x))

    即给定多项式(A(x),B(x))

    [A(x) = sum_{i=0}^na_ix^i ]

    [B(x) = sum_{i=0}^nb_ix^i ]

    求一个多项式(C(x))满足

    [C(x) = sum_{i=0}^n(a_i-b_i)x^i ]

    系数对位相减,复杂度(O(n));

    计算多项式的乘积即(A(x) * B(x))

    即给定多项式(A(x),B(x))

    [A(x) = sum_{i=0}^na_ix^i ]

    [B(x) = sum_{i=0}^nb_ix^i ]

    求一个多项式(C(x))满足(C(x) = sum_{i=0}^{2n}c_ix^i)

    [c_i = sum_{j+k=i,0leq j,kleq n}a_jb_kx^i ]

    我们可以直接暴力计算,复杂度(O(n^2))
    我们之前说过点值表示法主要用于快速计算卷积.卷积通俗地来理解其实就是求所有满足(i+j)为一定值的一种计算形势。
    所以这里要计算的其实就是一个卷积。
    假设说我们多项式是采用的点值表示法,那么我们可以直接把两组点对((x_i,y_i))中所有的点对直接对位相乘,即得到了((x_i,y_{1_i}*y_{2_i}))
    这样我们就得到了结果多项式的点值表达方式,如果一直采用点值表达的话可以简化乘法运算,但是不利于观察多项式。(毕竟你不能直接看着一堆二元对当多项式用)
    所以我们需要一种快速将多项式表示形式转化的一种算法。FFT就是用来处理这个问题的,可以做到在(O(nlogn))内转化完成。所以就可以将乘法的复杂度降低到(O(nlogn))

    Cooley-Tukey

    该算法的证明及实现网上提到的很多,故不再说明。
    (我才不告诉你我不会证明呢)

    求多项式的逆元

    求多项式(A(x))((mod ext{ } x^n))意义下的逆元
    即对于一个给定的多项式(f(x)),求一个多项式(B(x))
    满足(A(x)B(x) equiv 1 (mod ext{ } x^n))
    我们仍然采用分治的思想.
    假设我们已知一个多项式(B^{'}(x))满足$$A(x)B^{'}(x) equiv 1(mod ext{ } x^{frac{n}{2}})$$
    且我们当前求的多项式(B(x))一定满足$$A(x)
    B^(x) equiv 1(mod ext{ } x^{frac{n}{2}})$$
    我们将上述两式作差,消掉(A(x))再平方,再乘上(A(x)),可以得到

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

    因为(A(x)B(x) equiv 1 (mod ext{ } x^n))
    所以最终化简得到$$B(x) = 2B^{'}(x) - A(x)B{'^2}(x)$$
    注:不要忘记在得到的多项式中将所有次数>=mod的次数的项置零.
    复杂度(O(nlogn))

    多项式的除法和取余

    问题即为:给定一个(n)次多项式(A(x))和一个(m)次多项式(B(x)),求出两个多项式(f(x),g(x))满足

    [A(x) = f(x)B(x) + g(x) ]

    且满足(deg_f leq n - m,deg_g < m)
    我们发现,如果我们能消去(g(x)),就可以人为地规定一个mod,做逆元即可。
    所以我们考虑如何消去(g(x))一项.
    我们首先定义(f^R(x) = x^nf(frac{1}{x}))
    其中(f^R(x))即为(f(x))的系数反转后得到的新的多项式。
    (不理解可以自己写几个多项式试一试)
    那么我们回到原式的(A(x) = f(x)B(x) + g(x))
    我们首先可以不失一般性地设(deg_f = n-m,deg_g = m-1)不足则补零.
    然后可以做如下变换:

    [A(x) = f(x)B(x) + g(x) ]

    [A(frac{1}{x}) = f(frac{1}{x})B(frac{1}{x}) + g(frac{1}{x}) ]

    [x^nA(frac{1}{x}) = x^{n-m}f(frac{1}{x})*x^mB(frac{1}{x}) + x^{n-m+1}*x^{m-1}g(frac{1}{x}) ]

    [A^R(x) = f^R(x)B^R(x) + x^{n-m+1}g^R(x) ]

    然后我们把这个式子放在(mod ext{ } x^{n-m+1})的剩余系下,可以证明只有最后一项会被模去。
    于是我们得到了这么一个式子(A^R(x) equiv f^R(x)B^R(x) (mod ext{ } x^{n-m+1}))
    所以我们用刚刚提到的方法求出(B(x))的逆元即可计算出(f(x))
    然后(g(x))的计算就不用多说了吧,(f(x))都有了...
    复杂度(O(nlogn))

    多项式的多点求值

    问题即为:给出一个多项式(A(x))(n)个点(x_0,x_1,...,x_{n-1})(A(x_0),A(x_1),...,A(s_{n-1}))
    实际上就是多项式向系数表达项点值表达上更一般的转化。由于不具有单位根的特殊性,故不能使用FFT.
    但是我们依然可以从Cooley-Tukey的分治策略的角度上想:
    我们考虑把求值序列和系数都分半,本别记分开的左右序列为(X,Y)
    即:

    [X = {x_0,x_1,...,x_{frac{n}{2}}} ]

    [Y = {x_{frac{n}{2}+1},x_{frac{n}{2}+2},...,x_{n-1}} ]

    我们设用(X)插值得到的序列为(f(x)),用(y)插值得到的序列为(g(x))
    那么我们考虑将其合并为一个新的多项式.
    首先我们构造这样的两个多项式

    [F(x) = prod_{i=0}^{frac{n}{2}}(x - x_i) ]

    [G(x) = prod_{i=frac{n}{2}+1}{n-1}(x-x_i) ]

    注:因下面的两部分完全相同,故只说明(F(x))的部分
    这样,(F(x))的值为零当且仅当(x in X)
    那么我们可以有(A(x) = C(x)F(x) + f(x))
    这样的话在(x in X)(C(x)F(x))一定为零,因此可以让(f(x))直接对(A(x))做出贡献
    所以此时有(A(x) equiv f(x) (mod ext{ } F(x)))
    多项式取余即可.
    这样完成了子问题的合并,复杂度(O(nlog^2n))

    多项式的快速插值

    问题即为:给出(n+1)个二元数对((x_i,y_i)),要求求出一个(n)次多项式使所有的二元数对都在这个多项式上。
    实际上就是多项式的点值表达向系数表达上更一般的转化。由于不具有单位根的特殊性,故不能使用FFT.
    但是我们依然可以从Cooley-Tukey的分治策略的角度上想:
    我们考虑把求值序列分半,本别记分开的左右序列为(X,Y)
    即:

    [X = {(x_0,y_0),(x_1,y_1),...,(x_{frac{n}{2}},y_{frac{n}{2}})} ]

    [Y = {(x_{frac{n}{2}+1},y_{frac{n}{2}+1}),(x_{frac{n}{2}+2},y_{frac{n}{2}+2}),...,(x_{n-1},y_{n-1})} ]

    我们设用(X)插值得到的多项式为(f(x)),用(y)插值得到的序列为(g(x)),考虑构造(A(x))
    依然设$$F(x) = prod_{i=0}^{frac{n}{2}}(x - x_i)$$
    (A(x) = g(x)F(x) + f(x))
    那么现在问题就变为了将所有在(Y)中的点插值,使得

    [forall(x_i,y_i) in Y,y_i = g(x)F(x) + f(x) ]

    化简得

    [g(x_i) = frac{f(x_i) - y_i}{F(x_i)} ]

    所以我们完成了子问题的递归。
    复杂度(O(nlog^3n))

    多项式求(ln)

    问题即为:给定多项式(f(x))求一个多项式(g(x))满足(g(x) = ln ext{ }f(x))
    我们将两边都求导可得

    [g(x) = frac{f^{'}(x)}{f(x)} ]

    所以可以在(O(nlogn))内完成

    多项式求(exp)

    问题即为:给定一个多项式(f(x)),求一个多项式(g(x))满足(g(x) = e^f(x))
    我们采用分治策略.
    (这个公式我不会证)
    (g_0(x))为子问题((1~frac{n}{2}))中得到的结果
    则我们有(g(x) = g_0(x)*(1 - lng_0(x) + f(x)))
    迭代可以在(O(nlog^2n))内完成

    多项式开根号

    问题即为:给定一个多项式(f(x))求出一个多项式(g(x))满足g^2(x) = f(x)
    我们设(g_0^2(x) equiv f(x)(mod ext{ } x^{frac{n}{2}}))
    呢么我们有

    [(g_0^2(x) - f(x))^2 equiv 0 (mod ext{ } x^n) ]

    [(g_0^2(x) + f(x))^2 equiv 4g_0^2(x)f(x) (mod ext{ } x^n) ]

    [(frac{g_0^2(x) + f(x)}{2g_0(x)})^2 equiv f(x) (mod ext{ } x^n) ]

    那么我们就发现:

    [g(x) = (frac{g_0^2(x) + f(x)}{2g_0(x)}) ]

    利用分治算法,我们可以在(O(nlogn))内完成
    但是常数巨大,我们可以把常数也算成一个(log),也就是(O(nlog^2n))

    多项式快速幂

    问题即为:给定一个多项式(f(x))和一个整数(k)求一个多项式(g(x))满足(g(x) equiv f^k(x) (mod ext{ } x^n))
    正常选手: 我们可以多次FFT每次除去所有次数>=n的项。
    脑洞选手: 我们可以输出(exp(k ext{ }ln ext{ }f(x)))

    Code:

    #include <cmath>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    template<typename T>inline void read(T &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    const int pri_rt = 3;
    const int maxn=600010;
    const int mod=998244353;
    const int inv_2 = 499122177;
    int n,k,N,C,len;
    int rev[maxn],w[maxn];
    int f[maxn];
    int Inv[maxn],Ln[maxn],Exp[maxn],Sqrt[maxn];
    inline int qpow(int x,int p){
    	int ret = 1;
    	for(;p;x=1LL*x*x%mod,p>>=1) if(p&1) ret=1LL*ret*x%mod;
    	return ret;
    }
    inline int check(int &x){
    	if(x < 0) x += mod;
    	if(x >= mod) x -= mod;
    }
    void FNT(int n,int *x,int flag){
    	for(int i=0,t=0;i<n;++i){
    		if(i > t) swap(x[i],x[t]);
    		for(int j=n>>1;(t^=j) < j;j>>=1);
    	}
    	for(int m=2;m<=n;m<<=1){
    		int k = m>>1;
    		int wn = qpow(pri_rt,flag == 1 ? (mod - 1)/m : (mod-1) - (mod-1)/m);
    		w[0] = 1;
    		for(int i=1;i<k;++i) w[i] = 1LL*w[i-1]*wn % mod;
    		for(int i=0;i<n;i+=m){
    			for(int j=0;j<k;++j){
    				int u = 1LL*x[i+j+k]*w[j] % mod;
    				x[i+j+k] = x[i+j] - u;check(x[i+j+k]);
    				x[i+j] = x[i+j] + u;check(x[i+j]);
    			}
    		}
    	}
    	if(flag == -1){
    		int inv = qpow(n,mod-2);
    		for(int i=0;i<n;++i) x[i] = 1LL*x[i]*inv%mod;
    	}
    }
    inline void get_dao(int n,int *f){
    	for(int i=0;i<n;++i) f[i] = 1LL*f[i+1]*(i+1) % mod;
    	f[n] = 0;
    }
    inline void get_fen(int n,int *f){
    	for(int i=n-1;i>=0;--i) f[i] = 1LL*f[i-1]*qpow(i,mod-2) % mod;
    	f[0] = 0;
    }
    void get_inv(int n,int *f){
    	static int g[maxn];
    	if(n == 1){
    		Inv[0] = qpow(f[0],mod-2);
    		return;
    	}
    	get_inv((n+1)>>1,f);
    	int len = n<<1;
    	for(int i=0;i<n;++i) g[i] = f[i];
    	fill(g+n,g+len,0);
    	FNT(len,g,1);FNT(len,Inv,1);
    	for(int i=0;i<len;++i){
    		Inv[i] = 1LL*Inv[i]*(2LL - 1LL*g[i]*Inv[i]%mod + mod) % mod;
    	}FNT(len,Inv,-1);fill(Inv+n,Inv+len,0);
    }
    void get_ln(int n,int *f){
    	int len = n<<1;
    	fill(Inv,Inv+(len<<1),0);
    	get_inv(n,f);get_dao(n,f);
    	FNT(len,f,1);FNT(len,Inv,1);
    	for(int i=0;i<len;++i) Ln[i] = 1LL*f[i]*Inv[i] % mod;
    	FNT(len,Ln,-1);fill(Ln+n,Ln+len,0);
    	get_fen(n,Ln);
    }
    void get_exp(int n,int *f){
    	static int g[maxn];
    	if(n == 1){
    		Exp[0] = 1;
    		return;
    	}
    	get_exp(n>>1,f);
    	int len = n<<1;
    	for(int i=0;i<n;++i) g[i] = Exp[i];
    	fill(g+n,g+len,0);
    	get_ln(n,g);
    	for(int i=0;i<n;++i) Ln[i] = ((i == 0) - Ln[i] + f[i] + mod) % mod;
    	FNT(len,Ln,1);FNT(len,Exp,1);
    	for(int i=0;i<len;++i) Exp[i] = 1LL*Exp[i]*Ln[i] % mod;	
    	FNT(len,Exp,-1);fill(Exp+n,Exp+len,0);
    }
    void get_sqrt(int n,int *f){
    	static int g[maxn];
    	if(n == 1){
    		Sqrt[0] = sqrt(f[0]);
    		return;
    	}
    	get_sqrt(n>>1,f);
    	int len = n<<1;
    	fill(Inv,Inv+(len<<1),0);get_inv(n,Sqrt);
    	for(int i=0;i<n;++i) g[i] = f[i];
    	fill(g+n,g+len,0);
    	FNT(len,g,1);FNT(len,Inv,1);
    	for(int i=0;i<len;++i) g[i] = 1LL*g[i]*Inv[i] % mod;
    	FNT(len,g,-1);
    	for(int i=0;i<n;++i) Sqrt[i] = 1LL*(Sqrt[i] + g[i])*inv_2%mod;
    }
    void get_pow(int len,int *f,int p){
    	get_ln(len,f);
    	for(int i=0;i<len;++i) f[i] = 1LL*p*Ln[i]%mod;
    	fill(Exp,Exp+(len<<1),0);
    	get_exp(len,f);
    }
    int main(){
    	int n,k;read(n);read(k);
    	for(int i=0;i<n;++i) read(f[i]);
    	for(len=1;len<=n;len<<=1);
    	get_sqrt(len,f);
    	
    	fill(Inv,Inv+(len<<1),0);get_inv(len,Sqrt);
    	for(int i=0;i<len;++i) f[i] = Inv[i];
    
    	get_fen(len,f);fill(f+n,f+len,0);
    	
    	get_exp(len,f);
    	for(int i=0;i<len;++i) f[i] = Exp[i];
    
    	fill(Inv,Inv+(len<<1),0);get_inv(len,f);
    	for(int i=0;i<len;++i) f[i] = Inv[i];
    	++f[0];
    
    	get_ln(len,f);
    	for(int i=0;i<len;++i) f[i] = Ln[i];
    	++f[0];
    
    	fill(f+len+1,f+(len<<1),0);
    
    	get_pow(len,f,k);
    
    	for(int i=1;i<n;++i) printf("%d ",1LL*Exp[i]*i % mod);
    	puts("0");
    	getchar();getchar();
    	return 0;
    }
    

    该代码用于计算这样的一个式子

  • 相关阅读:
    理财-4
    “大锅”遇险记
    今日份灵感开发
    持续集成简介
    redis clusert分布式集群
    redis 哨兵
    redis 主从复制
    Redis新特性ACL安全策略
    redis 快照持久化RDB和AOF
    redis 基础常用命令
  • 原文地址:https://www.cnblogs.com/Skyminer/p/6399320.html
Copyright © 2011-2022 走看看