• 多项式膜法(持续更新!)


    前言

    本篇文章为基础的多项式计算的集合。

    文章中涉及到的多项式计算,通常会在生成函数中使用到。

    另外,模板题可以去洛谷上面找,一搜就有。

    代码有一点需要注意:如果你看到上下文中代码的调用方式不一样,不要惊慌。

    这是因为,我的写法是,给每个运算开辟一个 namespace 。如果是牛顿迭代法计算的话,我会再重载一个接口函数。

    牛顿迭代法的接口长成这个样子:

    void 运算名( int *ret, int *A, const int n )    //分别表示“返回值”,“输入多项式”和“多项式长度”
    {
          清空运算辅助数组;
          复制一遍 A ;
          运算名( n );        //这个就是我下文将给出的函数,只有一个参数,表示“多项式长度”
          将答案复制到 ret 里面;
    }
    

    多项式乘法

    这是所有多项式计算的基础,由于真的很基础,所以直接贴代码了。

    喜闻乐见的FFT:

    void FFT( comp *coe, int len, int type )
    {
    	int lg2 = log2( len );
    	for( int i = 0, rev ; i < len ; i ++ )
    	{
    		rev = 0;
    		for( int j = 0 ; j < lg2 ; j ++ ) rev |= ( ( i >> j ) & 1 ) << ( lg2 - j - 1 );
    		if( rev < i ) swapp( coe[rev], coe[i] );
    	}
    	comp wp, w, wo, we;
    	for( int s = 2, p ; s <= len ; s <<= 1 )
    	{
    		p = s >> 1, wp = comp( cos( type * PI / p ), sin( type * PI / p ) );
    		for( int i = 0 ; i < len ; i += s )
    		{
    			w = comp( 1, 0 );
    			for( int j = 0 ; j < p ; j ++, w *= wp )
    			{
    				we = coe[i + j], wo = coe[i + j + p];
    				coe[i + j] = we + wo * w;
    				coe[i + j + p] = we - wo * w; 
    			}
    		}
    	}
    	if( ~ type ) return ;
    	for( int i = 0 ; i <= len ; i ++ ) coe[i] /= len;
    }
    

    喜闻乐见的NTT,使用 998244353 作为模数,顺手预处理了单位根:

    void init( const int len )
    {
    	int lg2 = log2( len );
    	for( int i = 0 ; i < len ; i ++ )
    		for( int j = 0 ; j < lg2 ; j ++ )
    			rev[i] |= ( i >> j & 1 ) << ( lg2 - j - 1 );
    	wp[0] = 1, wp[1] = qkpow( g, phi / len );
    	wpinv[0] = 1, wpinv[1] = qkpow( g, phi - phi / len );
    	for( int i = 2 ; i < len ; i ++ )
    		wp[i] = 1ll * wp[i - 1] * wp[1] % mod,
    		wpinv[i] = 1ll * wpinv[i - 1] * wpinv[1] % mod;
    }
    
    void NTT( int *coe, const int len, const int t )
    {
    	#define p ( s >> 1 )
    	for( int i = 0 ; i < len ; i ++ )
    		if( rev[i] < i )
    			swapp( coe[i], coe[rev[i]] );
    	int w, wo, we;
    	for( int s = 2 ; s <= len ; s <<= 1 )
    		for( int i = 0 ; i < len ; i += s )
    			for( int j = 0 ; j < s >> 1 ; j ++ )
    			{
    				w = t > 0 ? wp[len / s * j] : wpinv[len / s * j];
    				we = coe[i + j], wo = 1ll * coe[i + j + p] * w % mod;
    				coe[i + j] = ( we + wo ) % mod, coe[i + j + p] = ( we - wo + mod ) % mod;
    			}
    	#undef p
    	if( t > 0 ) return; int inver = inv( len );
    	for( int i = 0 ; i < len ; i ++ ) coe[i] = 1ll * coe[i] * inver % mod;
    }   
    

    多项式牛顿迭代法

    其实这个不应该叫做“牛顿迭代”,而应该叫做套路倍增。

    一般情况下的牛顿迭代法可以求出(f(x)=0)的根,多项式牛顿迭代就可以求出(G(F(x))=0)的根。

    方法是,使用倍增,考虑我们已经求出了:

    [G(F_0(x))equiv 0pmod {x^{lceilfrac n 2 ceil}} ]

    现在我们想要得到:

    [G(F(x))equiv 0pmod {x^n} ]

    那么,我们用泰勒公式把(G)展开:

    [G(F(x))=sum_{k=0}^{+infty}frac{G^{(k)}(F_0(x))}{k!}(F(x)-F_0(x))^k ]

    注意到,(F(x))在低次下也是方程的根:

    [G(F(x))equiv 0pmod {x^{lceilfrac n 2 ceil}} ]

    那么做差就可以得到:

    [G(F(x))-G(F_0(x))equiv 0pmod {x^{lceilfrac n 2 ceil}} ]

    (G)不应该是一个常函数,那么就应该有:

    [F(x)-F_0(x)equiv 0pmod {x^{lceilfrac n 2 ceil}} ]

    考虑左右平方。由于(F(x)-F_0(x))的后(lceilfrac n 2 ceil)项都是 0 ,那么平方后的式子的后(n)项,卷积得到结果也都应该是 0 (乘法时只要一个因数为 0 结果都是 0)。

    于是就有:

    [(F(x)-F_0(x))^2equiv 0pmod {x^n} ]

    同样可知,更高次的幂在模意义下也应该是 0 。

    然后泰勒公式就可以化简得到:

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

    简单地整理一下得到:

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

    奇妙吧,我也觉得。

    多项式乘法逆

    问题是,给定一个(A(x)),求一个(A^{-1}(x)),使得(A(x)A^{-1}(x)equiv 1pmod {x^n})

    尝试倍增,假设我们已经有了:

    [A(x)F_0(x)equiv 1pmod {x^{lceilfrac n 2 ceil}} ]

    然后要求:

    [A(x)F(x)equiv 1pmod {x^n} ]

    然后通过牛顿迭代法部分的分析,我们知道应该有:

    [(F(x)-F_0(x))^2equiv 0pmod {x^n} ]

    展开,并且两边同时乘上(A(x))有:

    [F(x)-2F_0(x)+A(x)F_0^2(x)equiv 0pmod {x^n} ]

    简单变化得到:

    [F(x)equiv F_0(x)(2-A(x)F_0(x))pmod {x^n} ]

    时间复杂度应该是(T(n)=T(frac n 2)+O(nlog_2n)=O(nlog_2n))

    但是常数比较大,下面粘一份代码:

    int P[MAXN], F0[MAXN], F[MAXN], B[MAXN];
    
    void PolyInv( const int n )
    {
    	if( n == 1 ) { F[0] = inv( P[0] ); return; }
    	int p = n + 1 >> 1, len = 1;
    	PolyInv( p );
    	while( len < n << 1 ) len <<= 1; init( len );
    	for( int i = 0 ; i < n ; i ++ ) B[i] = P[i];
    	for( int i = 0 ; i < len ; i ++ ) F0[i] = F[i], F[i] = 0;
    	NTT( B, len, 1 ), NTT( F0, len, 1 );
    	for( int i = 0 ; i < len ; i ++ ) F[i] = 1ll * F0[i] * ( 2 - 1ll * B[i] * F0[i] % mod + mod ) % mod;
    	NTT( F, len, -1 );
    	for( int i = n ; i < len ; i ++ ) F[i] = 0;
    }
    

    多项式带余除法

    给定(n)次多项式(A(x))(m)次多项式(B(x))(m<n),要求(n-m)次多项式(C(x))低于(m)的多项式(R(x)),使得以下等式成立:

    [A(x)equiv B(x)C(x)+R(x)pmod {x^n} ]

    这还不简单,大除法谁不会?

    不难感受到,最恶心的部分是(R(x)),因而我们得想办法把它踢走。

    观察一下,我们发现,(R(x))至少前(n-m+1)次的系数都是 0 ,如果我们把系数在(pmod {x^n})意义下翻转,(R(x))就自然而然地消失了。

    翻转系数是比较简单的操作,大概就是:

    [x^nA(frac 1 x)equiv x^mB(frac 1 x)cdot x^{n-m}C(frac 1 x)+x^nR(frac 1 x)pmod {x^n} ]

    再见,(R(x))

    [x^nA(frac 1 x)equiv x^mB(frac 1 x)cdot x^{n-m}C(frac 1 x)pmod {x^n} ]

    然后用 多项式求逆 + 多项式乘法 就可以得到(x^{n-m}C(frac 1 x)),翻转之后再做一下乘法就可以得到(R(x))

    时间复杂度是喜闻乐见的(O(nlog_2n))

    int G[MAXN], H[MAXN], A[MAXN], B[MAXN];
    int N, M;
    
    void PolyMul( int *ret, int *a, const int La, int *b, const int Lb )
    {
    	int len = 1;
    	while( len < La + Lb ) len <<= 1;
    	init( len );
    	
    	for( int i = 0 ; i < len ; i ++ ) ret[i] = A[i] = B[i] = 0;
    	for( int i = 0 ; i < La ; i ++ ) A[i] = a[i];
    	for( int i = 0 ; i < Lb ; i ++ ) B[i] = b[i];
    	NTT( A, len, 1 ), NTT( B, len, 1 );
    	for( int i = 0 ; i < len ; i ++ ) ret[i] = 1ll * A[i] * B[i] % mod;
    	NTT( ret, len, -1 );
    	for( int i = La + Lb ; i < len ; i ++ ) ret[i] = 0;
    }
    
    int main()
    {
          ...
          std :: reverse( G, G + N );
          std :: reverse( H, H + M );
          PolyInv :: PolyInv( HInv, H, N - M + 1 );
          PolyMul :: PolyMul( C, G, N, HInv, N - M + 1 );
          for( int i = N - M + 1 ; i < 2 * N - M + 1 ; i ++ ) C[i] = 0; 
          std :: reverse( C, C + N - M + 1 );
          std :: reverse( H, H + M );
          std :: reverse( G, G + N );
          PolyMul :: PolyMul( R, H, M, C, N - M + 1 );
          for( int i = 0 ; i < N ; i ++ ) R[i] = ( G[i] - R[i] + mod ) % mod;
          ...
    }
    

    多项式 ln

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

    没想到多项式还有初等函数吧,我也没有想到。

    这个还比较简单,众所周知:

    [ln x=int frac{mathrm d x}{x} ]

    因此,对原式子两边求导:

    [B'(x)equiv frac{A'(x)}{A(x)}pmod {x^n} ]

    其中多项式的积分和导数可以直接算,再套上一个求逆,我们就得到了(ln A(x))。时间复杂度(O(nlog_2n))

    设多项式(A(x)=sum_{i=0} a_ix^i),它的导数是:

    [A'(x)=sum_{i=1} ia_ix^{i-1} ]

    积分是:

    [int A(x) mathrm d x=sum_{i=0}frac {a_i} {i+1} x^{i+1} + C ]

    模板题里面,保证了(a_0=1),因此积分中(C=0)不然我也不知道怎么计算

    int inv[MAXN], B[MAXN], C[MAXN], D[MAXN];
    
    void PolyDer( int *ret, int *A, const int len )
    {
    	for( int i = 0 ; i < len - 1 ; i ++ ) 
    		ret[i] = 1ll * A[i + 1] * ( i + 1 ) % mod;
    	ret[len - 1] = 0;
    }
    
    void PolyInt( int *ret, int *A, const int len )
    {
    	inv[1] = 1;
    	for( int i = 2 ; i <= len ; i ++ )
    		inv[i] = 1ll * inv[mod % i] * ( mod - mod / i ) % mod;
            for( int i = 1 ; i <= len ; i ++ )
    		ret[i] = 1ll * A[i - 1] * inv[i] % mod;
    	ret[0] = 0;
    }
    
    void PolyLn( int *ret, int *A, const int n )
    {
    	PolyDer :: PolyDer( B, A, n );
    	PolyInv :: PolyInv( C, A, n );
    	int len = 1;
    	while( len <= n << 1 ) len <<= 1;
    	init( len ); 
    	NTT( B, len, 1 ), NTT( C, len, 1 );
    	for( int i = 0 ; i < len ; i ++ ) D[i] = 1ll * B[i] * C[i] % mod;
    	NTT( D, len, -1 );
    	for( int i = n ; i < len ; i ++ ) D[i] = 0;
    	PolyInt :: PolyInt( ret, D, n - 1 );
    	for( int i = n ; i < len ; i ++ ) ret[i] = 0;
    }
    

    多项式 exp

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

    两边求导:

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

    然后就可以尝试解方程:

    [G(F(x))equiv ln F(x)-A(x)equiv 0pmod {x^n} ]

    套用牛顿迭代法:

    [egin{aligned} &F(x)equiv F_0(x)-frac{ln F_0(x)-A(x)}{F_0^{-1}(x)}&pmod {x^n}\ Rightarrow & F(x)equiv F_0(x)(1-ln F_0(x)+A(x))&pmod {x^n} end{aligned} ]

    然后倍增计算,时间复杂度(T(n)=T(frac n 2)+O(nlog_2n)=O(nlog_2n))

    常数嘛......你懂的

    int P[MAXN], F0[MAXN], F[MAXN], B[MAXN];
    	
    void PolyExp( const int n )
    {
    	if( n == 1 ) { F[0] = 1; return ; }
    	int p = n + 1 >> 1, len = 1;
    	PolyExp( p );
    	while( len < ( n << 1 ) ) len <<= 1; init( len );
    	for( int i = 0 ; i < len ; i ++ ) F0[i] = F[i], F[i] = 0;
    	PolyLn :: PolyLn( B, F0, n );
    	for( int i = 0 ; i < n ; i ++ ) B[i] = ( P[i] - B[i] + mod ) % mod;
    	B[0] = ( B[0] + 1 ) % mod, NTT( B, len, 1 ), NTT( F0, len, 1 );
    	for( int i = 0 ; i < len ; i ++ ) F[i] = 1ll * F0[i] * B[i] % mod;
    	NTT( F, len, -1 );
    	for( int i = n ; i < len ; i ++ ) F[i] = 0;
    }
    

    多项式快速幂

    给定(A(x))和正整数(k),求(B(x)equiv (A(x))^kpmod {x^n})

    两边(ln)一下就有:

    [ln B(x)equiv kA(x)pmod {x^n} ]

    套板子计算一下就好,时间虽然(O(nlog_2n))

    但常数就更大了

    int A[MAXN], LnA[MAXN];
    
    void PolyQkpow( int *ret, int *a, const int l1, const int indx )
    {
    	for( int i = 0 ; i < l1 ; i ++ ) A[i] = a[i];
    	PolyLn :: PolyLn( LnA, A, l1 );
    	for( int i = 0 ; i < l1 ; i ++ ) LnA[i] = 1ll * LnA[i] * indx % mod;
    	PolyExp :: PolyExp( ret, LnA, l1 );
    }
    

    多项式开根

    给定(A(x)),求(B(x)equiv sqrt{A(x)}pmod {x^n})

    问题等价于求下面函数的一个根:

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

    套用牛顿迭代公式:

    [egin{aligned} F(x)&equiv F_0(x)-frac{(F_0(x))^2-A(x)}{2F_0(x)}&pmod {x^n}\ Rightarrow F(x)&equiv frac{F_0(x)+frac{A(x)}{F_0(x)}}{2}&pmod {x^n} end{aligned} ]

    时间复杂度(O(nlog_2n))

    需要注意的是,如果(A)的常数项不是 1 ,那么我们就需要用二次剩余计算出(B)的常数项。代码中给出的是(A)的常数项是 1 的情况。

    代码:

    int P[MAXN], F0[MAXN], F[MAXN], B[MAXN], Finv[MAXN];
    
    void PolySqrt( const int n )
    {
    	if( n == 1 ) { F[0] = 1; return ; }
    	int half = n + 1 >> 1, len = 1;
    	PolySqrt( half );
    	while( len < ( n << 1 ) ) len <<= 1;
    	for( int i = 0 ; i < n ; i ++ ) B[i] = P[i];
    	for( int i = 0 ; i < len ; i ++ ) F0[i] = F[i], F[i] = 0;
    	PolyInv :: PolyInv( Finv, F0, n );
    	NTT( Finv, len, 1 ), NTT( B, len, 1 );
    	for( int i = 0 ; i < len ; i ++ ) F[i] = 1ll * Finv[i] * B[i] % mod;
            NTT( F, len, -1 );
    	for( int i = n ; i < len ; i ++ ) F[i] = 0;
    	for( int i = 0 ; i < n ; i ++ ) F[i] = 1ll * ( F[i] + F0[i] ) % mod * inv2 % mod;
    }
    

    更多

    咕咕咕

    看!鸽子!

  • 相关阅读:
    办公室搞笑记(2) 李姐
    世界上疼我的人又少了一个
    带给杨帆的祝福:)
    火:) 火:) 火:)
    我们都是享受寂寞的孩子:)
    복 경 에 갑 니 다 :) 去北京.
    너는 겨울이 좋아요 .我喜欢冬天:)
    2007年:新年,新开始:)
    Nginx 泛域名配置方式
    数据库设计 从零开始系列之一
  • 原文地址:https://www.cnblogs.com/crashed/p/13283455.html
Copyright © 2020-2023  润新知