[学习笔记]多项式的各种操作

目录

  • 多项式求逆
  • 多项式除法/取模
  • 多项式牛顿迭代法
  • 多项式开根
  • 多项式 ln
  • 多项式 exp
  • 多项式幂函数

多项式求逆

点此看题

可以暴力递推,时间复杂度 O ( n 2 ) O(n^2)

倍增可以将之优化到 O ( n log n ) O(n\log n) ,我们先从 1 1 开始倍增,假设现在倍增到了 p p ,得到了模 x p x^p 的逆元 B ( x ) B'(x) ,现在我们要求 B ( x ) B(x) ,显然这时候求出来的 B ( x ) B'(x) B ( x ) B(x) 的一部分,有下列关系式:
B ( x ) B ( x ) = 0 m o d    x p B(x)-B'(x)=0\mod x^{p} 考虑 B ( x ) B ( x ) B(x)-B'(x) 的结果一定是形如 0 + 0 x + 0 x 2 . . . . + a p x p . . . . 0+0x+0x^2....+a_px^p.... ,怎么把模数扩展到 2 p 2p 呢,可以考虑将它平方,那么平方过的结果形如 0 + 0 x + 0 x 2 . . . . + 0 x 2 p 1 + a 2 p x 2 p 0+0x+0x^2....+0x^{2p-1}+a_{2p}x^{2p} ,有如下关系式:
( B ( x ) B ( x ) ) 2 = 0 m o d    x 2 p (B(x)-B'(x))^2=0\mod x^{2p} B ( x ) 2 + B ( x ) 2 2 B ( x ) B ( x ) = 0 m o d    x 2 p B(x)^2+B'(x)^2-2B(x)B'(x)=0\mod x^{2p} B ( x ) + B ( x ) 2 A ( x ) 2 B ( x ) = 0 m o d    x 2 p B(x)+B'(x)^2A(x)-2B'(x)=0\mod x^{2p} B ( x ) = 2 B ( x ) B ( x ) 2 A ( x ) m o d    x 2 p B(x)=2B'(x)-B'(x)^2A(x)\mod x^{2p} 然后就可以用卷积算了,时间复杂度 O ( n log n + n 2 log n 2 . . . . . ) = O ( n log n ) O(n\log n+\frac{n}{2}\log \frac{n}{2}.....)=O(n\log n)

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1000005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,len,cur,lg,a[MAXN],b[MAXN],A[MAXN],B[MAXN],Rev[MAXN];
int qkpow(int a,int b)
{
	int res=1;
	while(b>0)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
void NTT(int *a,int len,int tmp)
{
	lg=(int)round(log2(len));
	for(int i=0;i<len;i++)
	{
		Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
		if(i<Rev[i])
			swap(a[i],a[Rev[i]]);
	}
	for(int s=2;s<=len;s<<=1)
	{
		int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
		for(int i=0;i<len;i+=s)
		{
			int x=1;
			for(int j=0;j<t;j++,x=x*w%MOD)
			{
				int fe=a[i+j],fo=a[i+j+t];
				a[i+j]=(fe+x*fo)%MOD;
				a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
			}
		}
	}
	if(tmp==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
void work(int n)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
signed main()
{
	n=read();
	for(int i=0;i<n;i++) a[i]=read();
	b[0]=qkpow(a[0],MOD-2);
	cur=1;
	while(cur<n)
	{
		cur<<=1;
		work(cur);
	}
	for(int i=0;i<n;i++)
		printf("%lld ",b[i]);
}

多项式除法

点此看题

给定 n n 次多项式 A ( x ) A(x) m m 次多项式 B ( x ) B(x) n > m n>m ),求出 n m n-m 次的商 C ( x ) C(x) m 1 m-1 次的余数 D ( x ) D(x)

由于这里有余数 D ( x ) D(x) 难于处理,我们可以考虑消去它的影响。

可以用一些奇技淫巧,比如说翻转多项式,我们来推一波柿子( R R 表示反转上标):
A ( 1 x ) = B ( 1 x ) C ( 1 x ) + D ( 1 x ) A(\frac{1}{x})=B(\frac{1}{x})C(\frac{1}{x})+D(\frac{1}{x}) x n A ( 1 x ) = x m B ( 1 x ) x n m C ( 1 x ) + x n D ( 1 x ) x^nA(\frac{1}{x})=x^mB(\frac{1}{x})\cdot x^{n-m}C(\frac{1}{x})+x^nD(\frac{1}{x}) A R ( x ) = B R ( x ) C R ( x ) + x n m + 1 D ( x ) A^R(x)=B^R(x)\cdot C^R(x)+x^{n-m+1}D(x) 然后我们把上式模一个 x n m + 1 x^{n-m+1} ,然后上市就变成了:
A R ( x ) = B R ( x ) C R ( x ) m o d    x n m + 1 A^R(x)=B^R(x)\cdot C^R(x)\mod x^{n-m+1} C R ( x ) = A R ( x ) B R ( x ) 1 m o d    x n m + 1 C^R(x)=A^R(x)\cdot B^R(x)^{-1}\mod x^{n-m+1} 然后求一个多项式逆元用 NTT \text{NTT} 即可,时间复杂度 O ( n log n ) O(n\log n)

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1000005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,m,k,len,lg,Rev[MAXN],A[MAXN],B[MAXN];
int a[MAXN],b[MAXN],c[MAXN],ar[MAXN],br[MAXN];
int qkpow(int a,int b)
{
	int res=1;
	while(b>0)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
void NTT(int *a,int len,int tmp)
{
	lg=(int)round(log2(len));
	for(int i=0;i<len;i++)
	{
		Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
		if(i<Rev[i])
			swap(a[i],a[Rev[i]]);
	}
	for(int s=2;s<=len;s<<=1)
	{
		int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
		for(int i=0;i<len;i+=s)
		{
			int x=1;
			for(int j=0;j<t;j++,x=x*w%MOD)
			{
				int fe=a[i+j],fo=a[i+j+t];
				a[i+j]=(fe+x*fo)%MOD;
				a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
			}
		}
	}
	if(tmp==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void inv(int *a)
{
	int cur=1,b[MAXN]={};
	b[0]=qkpow(a[0],MOD-2);
	while(cur<k+1)
	{
		cur<<=1;
		work(cur,a,b);
	}
	for(int i=0;i<=k;i++)
		a[i]=b[i];
}
void mul(int *a,int *b,int *c)
{
	len=1;while(len<=n+k) len<<=1;
	for(int i=0;i<=len;i++) A[i]=B[i]=0;
	for(int i=0;i<=n;i++) A[i]=a[i];
	for(int i=0;i<=k;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<=len;i++) A[i]=A[i]*B[i]%MOD;
	NTT(A,len,-1);
	for(int i=0;i<=k;i++)
		c[i]=A[i];
}
signed main()
{
	n=read();m=read();k=n-m;
	for(int i=0;i<=n;i++) a[i]=ar[n-i]=read();
	for(int i=0;i<=m;i++) b[i]=br[m-i]=read();
	inv(br);
	mul(ar,br,c);
	for(int i=0;i<=k/2;i++)
		swap(c[i],c[k-i]);
	for(int i=0;i<=k;i++)
		printf("%lld ",(c[i]%MOD+MOD)%MOD);
	puts("");
	//
	len=1;while(len<=2*n) len<<=1;
	NTT(a,len,1);NTT(b,len,1);NTT(c,len,1);
	for(int i=0;i<=len;i++) a[i]=a[i]-b[i]*c[i],a[i]%=MOD;
	NTT(a,len,-1);
	for(int i=0;i<m;i++)
		printf("%lld ",(a[i]%MOD+MOD)%MOD);
}

多项式牛顿迭代法

0x01 导数的芝士

下面的介绍引用自百度百科。

导数(Derivative),也叫导函数值。又名微商,是微积分中的重要基础概念。当函数y=f(x)的自变量x在一点x0上产生一个增量Δx时,函数输出值的增量Δy与自变量增量Δx的比值在Δx趋于0时的极限a如果存在,a即为在x0处的导数,记作f’(x0)或df(x0)/dx。

对于一个函数 f ( x ) = a 0 + a 1 x + a 2 x 2 . . . + a n x n f(x)=a_0+a_1x+a_2x^2...+a_nx^n ,它的导函数 f ( x ) = a 1 + 2 a 2 x . . . + n a n x n 1 f'(x)=a_1+2a_2x...+na_nx^{n-1}

0x02 泰勒展开的芝士

泰勒公式是将一个在x=x0处具有n阶导数的函数f(x)利用关于(x-x0)的n次多项式来逼近函数的方法。
若函数f(x)在包含x0的某个闭区间[a,b]上具有n阶导数,且在开区间(a,b)上具有(n+1)阶导数,则对闭区间[a,b]上任意一点x,成立下式:

其中, 表示f(x)的n阶导数,等号后的多项式称为函数f(x)在x0处的泰勒展开式,剩余的Rn(x)是泰勒公式的余项,是(x-x0)n的高阶无穷小。

0x03 牛顿迭代法

这个方法用来解决下列问题,有一个关于多项式 f ( x ) f(x) 的方程 g ( f ( x ) ) = 0 g(f(x))=0 ,给定 g g ,解这个方程。

假设我们已经求出了在模 p p 意义下的解 f 0 ( x ) f_0(x) ,考虑扩展到模 2 p 2p 意义下,把 g g f 0 ( x ) f_0(x) 处泰勒展开:
g ( f ( x ) ) = g ( f 0 ( x ) ) + g ( f 0 ( x ) ) ( f ( x ) f 0 ( x ) ) + g ( f 0 ( x ) ) 2 ! ( f ( x ) f 0 ( x ) ) 2 + . . . . . g(f(x))=g(f_0(x))+g'(f_0(x))(f(x)-f_0(x))+\frac{g''(f_0(x))}{2!}(f(x)-f_0(x))^2+..... 考虑到 f ( x ) f 0 ( x ) f(x)-f_0(x) 是形如 0 + 0 x + . . . + a p x p 0+0x+...+a_px^p 的形式,平方本质就是卷积,所以平方后被模 2 p 2p 的答案就是 0 0 ,泰勒展开第二项之后的项都会被模掉,所以柿子就变得简洁了:
g ( f ( x ) ) = g ( f 0 ( x ) ) + g ( f 0 ( x ) ) ( f ( x ) f 0 ( x ) ) = 0 m o d    x 2 p g(f(x))=g(f_0(x))+g'(f_0(x))(f(x)-f_0(x))=0\mod x^{2p} f ( x ) = f 0 ( x ) g ( f 0 ( x ) ) g ( f 0 ( x ) ) m o d    2 2 p f(x)=f_0(x)-\frac{g(f_0(x))}{g'(f_0(x))}\mod 2^{2p} 很多问题都能用这个方法,比如多项式求逆,多项式开根。

多项式开根

点此看题

给定多项式 A ( x ) A(x) ,求 B ( x ) B(x) ,满足 B ( x ) 2 A ( x ) = 0 B(x)^2-A(x)=0 ,为了套牛顿迭代法,我们设 g ( x ) = x 2 A ( y ) g(x)=x^2-A(y) (这里可以把 A A 当常数项, g ( x ) g(x) 的自变量是一个多项式),我们已经求出了在模 p p 的意义的解 B 0 ( x ) B_0(x) 有下列柿子:
B ( x ) = B 0 ( x ) B 0 ( x ) 2 A ( x ) 2 B 0 ( x ) B(x)=B_0(x)-\frac{B_0(x)^2-A(x)}{2B_0(x)} = 1 2 ( B 0 ( x ) + A ( x ) B 0 ( x ) ) m o d    x 2 p =\frac{1}{2}(B_0(x)+\frac{A(x)}{B_0(x)})\mod x^{2p} 时间复杂度 O ( n log n ) O(n\log n) A A 的常数项必须是 1 1 要不然我也没办法了

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1000005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,len,lg,Rev[MAXN];
int a[MAXN],b[MAXN],c[MAXN],A[MAXN],B[MAXN];
int qkpow(int a,int b)
{
	int res=1;
	while(b>0)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
void NTT(int *a,int len,int tmp)
{
	lg=(int)round(log2(len));
	for(int i=0;i<len;i++)
	{
		Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
		if(i<Rev[i])
			swap(a[i],a[Rev[i]]);
	}
	for(int s=2;s<=len;s<<=1)
	{
		int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
		for(int i=0;i<len;i+=s)
		{
			int x=1;
			for(int j=0;j<t;j++,x=x*w%MOD)
			{
				int fe=a[i+j],fo=a[i+j+t];
				a[i+j]=(fe+x*fo)%MOD;
				a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
			}
		}
	}
	if(tmp==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void inv(int n,int *a,int *b)
{
	int cur=1;
	for(int i=0;i<n;i++) b[i]=0;
	b[0]=qkpow(a[0],MOD-2);
	while(cur<n)
	{
		cur<<=1;
		work(cur,a,b);
	}
}
void mul(int n,int *a,int *b,int *c)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=A[i]*B[i]%MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++)
		c[i]=A[i];
}
signed main()
{
	n=read();
	for(int i=0;i<n;i++) a[i]=read();
	int cur=1,inv2=qkpow(2,MOD-2);b[0]=1;
	while(cur<n)
	{
		cur<<=1;
		inv(cur,b,c);
		mul(cur,a,c,c);
		for(int i=0;i<cur;i++)
			b[i]=inv2*(b[i]+c[i])%MOD;
	}
	for(int i=0;i<n;i++)
		printf("%lld ",b[i]);
}

updata 2019,12,22:

点此看题

刚学了扩展版的开根,等于普通开根加上对一个整数在模意义下开根,可以考虑使用原根,如果能找到是 3 b = a 0 m o d    998244353 3^b=a_0\mod 998244353 ,那么 b 0 b_0 就是 3 b / 2 3^{b/2} 3 3 998244353 998244353 的一个原根)。

处理类似于 y x = z m o d    p y^x=z\mod p 的柿子解 x x 可以用bsgs算法(其中 ( y , p ) = 1 (y,p)=1 ),我是看 y y b yyb 大佬的博客学的,先对柿子等价变化,设 x = a m b x=am-b ,则:
y a m = z y b m o d    p y^{am}=zy^b\mod p b b 的取值范围是 [ 0 , m 1 ] [0,m-1] ,我们先算出右边所有可能的值,放在 m a p map 里面,然后用所有可能的 a a 去看有没有能对的上的值,不难发现 m m p \sqrt p 的时候最优,时间复杂度 O ( p ) O(\sqrt p) ,关键代码。

int bsgs(int z)
{
    map<int,int> mp;
    int m=sqrt(MOD)+1,t=z,tt=0;
    for(int i=0;i<m;i++)
    {
        mp[t]=i;
        t=t*3%MOD;
    }
    t=tt=qkpow(3,m);
    for(int i=1;i<=m+1;i++)
    {
        if(mp.count(t))//查找t是否在mp中出现过
            return i*m-mp[t];
        t=t*tt%MOD;
    }
    return 0;
}
int solve(int x)
{
    int p=bsgs(x);
    int ret=qkpow(3,p/2);
    return min(ret,MOD-ret);//好像在这道版题中要求取更小的开方值
}

多项式求ln

点此看题

对于一个多项式 A ( x ) A(x) ,求 ln ( A ( x ) ) m o d    x n \ln(A(x)) \mod x^n ,先推一波柿子:
ln ( A ( x ) ) = ( ln ( A ( x ) ) ) = A ( x ) A ( x ) \ln(A(x))=\int(\ln(A(x)))'=\int \frac{A'(x)}{A(x)} 其中 \int 是积分符号,其中积分与求导是逆运算,下面来解释一下这个柿子需要的芝士:

  • ln ( x ) = 1 x \ln(x)'=\frac{1}{x}
  • g ( f ( x ) ) = g ( f ( x ) ) f ( x ) g(f(x))'=g'(f(x))\cdot f(x)' ,这就是链式法则

明确这个柿子之后就可以做了,关于 \int 运算的话把对应位乘上 ( i + 1 ) (i+1) 的逆元,就是求导的逆运算。

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1000005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,len,lg,Rev[MAXN];
int a[MAXN],b[MAXN],A[MAXN],B[MAXN];
int qkpow(int a,int b)
{
	int res=1;
	while(b>0)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
void NTT(int *a,int len,int tmp)
{
	lg=(int)round(log2(len));
	for(int i=0;i<len;i++)
	{
		Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
		if(i<Rev[i])
			swap(a[i],a[Rev[i]]);
	}
	for(int s=2;s<=len;s<<=1)
	{
		int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
		for(int i=0;i<len;i+=s)
		{
			int x=1;
			for(int j=0;j<t;j++,x=x*w%MOD)
			{
				int fe=a[i+j],fo=a[i+j+t];
				a[i+j]=(fe+x*fo)%MOD;
				a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
			}
		}
	}
	if(tmp==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void inv(int n,int *a,int *b)
{
	int cur=1;
	for(int i=0;i<n;i++) b[i]=0;
	b[0]=qkpow(a[0],MOD-2);
	while(cur<n)
	{
		cur<<=1;
		work(cur,a,b);
	}
}
void mul(int n,int *a,int *b,int *c)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=A[i]*B[i]%MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++)
		c[i]=A[i];
}
signed main()
{
	n=read();
	for(int i=0;i<n;i++) a[i]=read();
	inv(n,a,b);
	for(int i=0;i<n;i++)
		a[i]=a[i+1]*(i+1)%MOD;
	mul(n,a,b,a);
	printf("0");
	for(int i=0;i<n-1;i++)
		printf(" %lld",a[i]*qkpow(i+1,MOD-2)%MOD);
}

多项式exp

点此看题

对于一个多项式 A ( x ) A(x) ,求: e A ( x ) m o d    x n e^{A(x)}\mod x^n
B ( x ) = e A ( x ) m o d    x n B(x)=e^{A(x)}\mod x^n ln ( B ( x ) ) = A ( x ) m o d    x n \ln(B(x))=A(x)\mod x^n ln ( B ( x ) ) A ( x ) = 0 m o d    x n \ln(B(x))-A(x)=0\mod x^n 然后上面的柿子可以套牛顿迭代,上式就变成了:
B ( x ) = B 0 ( x ) ln ( B 0 ( x ) ) A ( x ) 1 B 0 ( x ) B(x)=B_0(x)-\frac{\ln(B_0(x))-A(x)}{\frac{1}{B_0(x)}} B ( x ) = B 0 ( A ( x ) ln ( B 0 ( x ) ) + 1 ) B(x)=B_0(A(x)-\ln(B_0(x))+1) A ( x ) A(x) 的常数项一定是 0 0 B ( x ) B(x) 的常数项一定是 1 1 ,不要问我为什么。

然后用一下上面的多项式ln,这个问题就很简单了。

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1000005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,len,lg,Rev[MAXN],A[MAXN],B[MAXN];
int a[MAXN],b[MAXN],c[MAXN]; 
int qkpow(int a,int b)
{
	int res=1;
	while(b>0)
	{
		if(b&1) res=res*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return res;
}
void NTT(int *a,int len,int tmp)
{
	lg=(int)round(log2(len));
	for(int i=0;i<len;i++)
	{
		Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
		if(i<Rev[i])
			swap(a[i],a[Rev[i]]);
	}
	for(int s=2;s<=len;s<<=1)
	{
		int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
		for(int i=0;i<len;i+=s)
		{
			int x=1;
			for(int j=0;j<t;j++,x=x*w%MOD)
			{
				int fe=a[i+j],fo=a[i+j+t];
				a[i+j]=(fe+x*fo)%MOD;
				a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
			}
		}
	}
	if(tmp==1) return ;
	int inv=qkpow(len,MOD-2);
	for(int i=0;i<len;i++)
		a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void inv(int n,int *a,int *b)
{
	int cur=1;
	for(int i=0;i<n;i++) b[i]=0;
	b[0]=qkpow(a[0],MOD-2);
	while(cur<n)
	{
		cur<<=1;
		work(cur,a,b);
	}
}
void mul(int n,int *a,int *b,int *c)
{
	len=1;while(len<=2*n) len<<=1;
	for(int i=0;i<len;i++) A[i]=B[i]=0;
	for(int i=0;i<n;i++) A[i]=a[i];
	for(int i=0;i<n;i++) B[i]=b[i];
	NTT(A,len,1);NTT(B,len,1);
	for(int i=0;i<len;i++) A[i]=A[i]*B[i]%MOD;
	NTT(A,len,-1);
	for(int i=0;i<n;i++)
		c[i]=A[i];
}
void ln(int n,int *A,int *c)
{
	int a[n]={},b[n]={};
	for(int i=0;i<n;i++)
		a[i]=A[i];
	inv(n,a,b);
	for(int i=0;i<n;i++)
		a[i]=a[i+1]*(i+1)%MOD;
	mul(n,a,b,a);
	c[0]=0;
	for(int i=1;i<n;i++)
		c[i]=a[i-1]*qkpow(i,MOD-2)%MOD;
}
signed main()
{
	n=read();
	for(int i=0;i<n;i++) a[i]=read();
	int cur=1;b[0]=1;
	while(cur<n)
	{
		cur<<=1;
		ln(cur,b,c);
		for(int i=0;i<cur;i++)
			c[i]=a[i]-c[i];
		c[0]++;
		len=1;while(len<=2*cur) len<<=1;
		NTT(b,len,1);NTT(c,len,1);
		for(int i=0;i<len;i++) b[i]=b[i]*c[i]%MOD;
		NTT(b,len,-1); 
	}
	for(int i=0;i<n;i++)
		printf("%lld ",b[i]);
}

多项式幂函数

点此看题

给定一个多项式 A ( x ) A(x) ,求它的 k k 次幂( k k 为正整数)。

很容易想到多项式直接快速幂,时间复杂度 O ( n log n log k ) O(n\log n\log k)

还有更快的方法,先考虑做一些等价变换:
A ( x ) k = e x p ( k ln ( A ( x ) ) ) A(x)^k=exp(k\ln(A(x))) 这样做的话时间复杂度是 O ( n log n ) O(n\log n) 的,前提要求是 A ( x ) A(x) 的常数项为 1 1

如果 A ( x ) A(x) 的常数项不为 1 1 呢,那我们就找到最低的有值的一位 d d ,然后就可以这样算:
A ( x ) k = a k x k d ( A ( x ) a x d ) k A(x)^k=a^kx^{kd}(\frac{A(x)}{ax^d})^k 然后就可以用常数项为 1 1 的方法做了,我的常数有点大,不开 O 2 O_2 竟然爆零,开了就能过。

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 1000005;
const int MOD = 998244353;
int read()
{
    int num=0,flag=1;
    char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),num%=MOD,c=getchar();
    return num*flag;
}
int n,k,len,lg,Rev[MAXN];
int a[MAXN],b[MAXN],c[MAXN],A[MAXN],B[MAXN];
int qkpow(int a,int b)
{
    int res=1;
    while(b>0)
    {
        if(b&1) res=res*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return res;
}
void NTT(int *a,int len,int tmp)
{
    lg=(int)round(log2(len));
    for(int i=0; i<len; i++)
    {
        Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
        if(i<Rev[i])
            swap(a[i],a[Rev[i]]);
    }
    for(int s=2; s<=len; s<<=1)
    {
        int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
        for(int i=0; i<len; i+=s)
        {
            int x=1;
            for(int j=0; j<t; j++,x=x*w%MOD)
            {
                int fe=a[i+j],fo=a[i+j+t];
                a[i+j]=(fe+x*fo)%MOD;
                a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
            }
        }
    }
    if(tmp==1) return ;
    int inv=qkpow(len,MOD-2);
    for(int i=0; i<len; i++)
        a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
    len=1;
    while(len<=2*n) len<<=1;
    for(int i=0; i<len; i++) A[i]=B[i]=0;
    for(int i=0; i<n; i++) A[i]=a[i];
    for(int i=0; i<n; i++) B[i]=b[i];
    NTT(A,len,1);
    NTT(B,len,1);
    for(int i=0; i<len; i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
    NTT(A,len,-1);
    for(int i=0; i<n; i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void inv(int n,int *a,int *b)
{
    int cur=1;
    for(int i=0; i<n; i++) b[i]=0;
    b[0]=qkpow(a[0],MOD-2);
    while(cur<n)
    {
        cur<<=1;
        work(cur,a,b);
    }
}
void mul(int n,int *a,int *b,int *c)
{
    len=1;
    while(len<=2*n) len<<=1;
    for(int i=0; i<len; i++) A[i]=B[i]=0;
    for(int i=0; i<n; i++) A[i]=a[i];
    for(int i=0; i<n; i++) B[i]=b[i];
    NTT(A,len,1);
    NTT(B,len,1);
    for(int i=0; i<len; i++) A[i]=A[i]*B[i]%MOD;
    NTT(A,len,-1);
    for(int i=0; i<n; i++)
        c[i]=A[i];
}
void ln(int n,int *A,int *c)
{
    int a[n*10]= {},b[n*10]= {};
    for(int i=0; i<n; i++)
        a[i]=A[i];
    inv(n,a,b);
    for(int i=0; i<n; i++)
        a[i]=a[i+1]*(i+1)%MOD;
    mul(n,a,b,a);
    c[0]=0;
    for(int i=1; i<n; i++)
        c[i]=a[i-1]*qkpow(i,MOD-2)%MOD;
}
void exp(int n,int *a,int *b)
{
    int cur=1,c[n*10]= {};
    b[0]=1;
    while(cur<n)
    {
        cur<<=1;
        ln(cur,b,c);
        for(int i=0; i<cur; i++)
            c[i]=a[i]-c[i];
        c[0]++;
        len=1;
        while(len<=2*cur) len<<=1;
        NTT(b,len,1);
        NTT(c,len,1);
        for(int i=0; i<len; i++) b[i]=b[i]*c[i]%MOD;
        NTT(b,len,-1);
    }
}
signed main()
{
    n=read();
    k=read();
    for(int i=0; i<n; i++)
        a[i]=read();
    for(int i=0; i<n; i++)
        if(a[i])
        {
            for(int j=i; j<n; j++)
                b[j-i]=a[j]*qkpow(a[i],MOD-2)%MOD;
            ln(n,b,c);
            for(int j=0; j<n; j++)
            {
                c[j]=c[j]*k%MOD;
                b[j]=0;
            }
            exp(n,c,b);
            int t=qkpow(a[i],k);
            for(int j=0; j<n; j++)
                c[j]=0;
            for(int j=0; j<n; j++)
                if(j+k*i<n)
                    c[j+k*i]=b[j]*t%MOD;
            for(int j=0; j<n; j++)
                printf("%lld ",c[j]);
            break;
        }
}
发布了192 篇原创文章 · 获赞 12 · 访问量 3370

猜你喜欢

转载自blog.csdn.net/C202044zxy/article/details/103631978