多项式全家桶

这里记录了我个人的多项式学习过程。
(可能在任何时候改成vector写法的代码,但不知道是什么时候)
下列过程中默认 n=2k

FFT

发现多项式 F(x) 在做乘法的时候复杂度为 O(n2) 的效率及其低下,对于许多题目是无法接受的。
在不好用系数去维护多项式的时候,考虑使用点值维护。
F(ωni),i=0,1,2n1 ,其中 ωn 表示的是 n 次单位根。
实现代码(这个是作者的古早码风,请见谅):

void DFT(Comp *f,int n,int rev)
{
	if(n==1) return;
	for(int i=0;i<n;i++) temp[i]=f[i];
	for(int i=0;i<n;i++)
		if(i&1) f[(i>>1)+hf]=temp[i];
		else f[i>>1]=temp[i];
	Comp *g=f,*h=f+hf;
	DFT(g,hf,rev);DFT(h,hf,rev);
	Comp cur,step;
	cur.img=0,cur.real=1;
	step.img=sin(2*M_PI*rev/n),step.real=cos(2*M_PI/n);
	for(int i=0;i<hf;i++)
	{
		temp[i]=g[i]+cur*h[i];
		temp[i+hf]=g[i]-cur*h[i];
		cur=cur*step;
	}
	for(int i=0;i<n;i++) f[i]=temp[i];
	return;
}

NTT

使用 double 和复数精度较低,同时有的题目需要对某些数取模,考虑单位根在模意义下的替代品——原根。
假设质数 p 的原根为 g ,我们可以取 gp1nn 次单位根。
常见的 998244353=223×119+1,g=31004535809=221479,g=3
一般使用蝴蝶变换和非递归写法,时间复杂度 O(nlogn)

void ntt(long long *num,int len,bool typ)
{
	long long g,w,x,y;
	for(int i=1;i<len;i++)
	{
		rev[i]=rev[i>>1]>>1;
		if(i&1) rev[i]|=len>>1;
	}
	for(int i=0;i<len;i++) if(rev[i]<i) swap(num[rev[i]],num[i]);
	for(int i=1;i<len;i<<=1)
	{
		if(typ) w=qpow(332748118,(Mod-1)/(i<<1));
		else w=qpow(3,(Mod-1)/(i<<1));
		for(int j=0;j<len;j+=(i<<1))
		{
			g=1;
			for(int k=0;k<i;k++)
			{
				x=num[j+k],y=g*num[j+i+k]%Mod;
				num[j+k]=(x+y)%Mod;
				num[j+i+k]=(x+Mod-y)%Mod;
				g=g*w%Mod;
			}
		}
	}
	if(typ)
	{
		long long ny=qpow(len,Mod-2);
		for(int i=0;i<len;i++) num[i]=num[i]*ny%Mod;
	}
	return;
}

多项式乘法逆

对于 F(x) ,求 G(x) ,使得 F(x)G(x)=1(modxn) ,对 998244353 取模。
考虑递归处理。
假设我们已知 F(x)G(x)=1(modxn2) ,又因为 F(x)G(x)=1(modxn2) ,所以 G(x)=G(x)(modxn2)
所以 G(x)G(x)=0(modxn2)
两侧同时取平方 (G(x)G(x))2=0(modxn)
展开得 G2(x)=2G(x)G(x)G2(x)(modxn)
两侧同时乘上 F(x) ,则 G(x)=2G(x)F(x)G2(x)(modxn)
G(x)=G(x)(2F(x)G(x))(modxn)
又因为在 n=1 时, F(x)G(x) 均只含常数项,所以 [x0]G(x)=1[x0]F(x)
时间复杂度 O(nlogn)
实现代码:

void polyinv(long long *F,long long *G,int lenth)//G<-1/F
{
    G[0]=qpow(F[0],Mod-2);
    int len;
    for(len=2;len<(lenth<<1);len<<=1)
    {
        for(int i=0;i<len;i++) f[i]=F[i],g[i]=G[i];
        for(int i=len;i<(len<<1);i++) f[i]=g[i]=0;
        ntt(f,len<<1,false),ntt(g,len<<1,false);
        for(int i=0;i<(len<<1);i++) f[i]=f[i]*g[i]%Mod;
        ntt(f,len<<1,true);
        for(int i=len;i<(len<<1);i++) f[i]=0;
        for(int i=0;i<len;i++) f[i]=f[i]?Mod-f[i]:0;
        f[0]=chk(f[0]+2);
        ntt(f,len<<1,false);
        for(int i=0;i<(len<<1);i++) f[i]=f[i]*g[i]%Mod;
        ntt(f,len<<1,true);
        for(int i=0;i<len;i++) G[i]=f[i];
    }
    for(int i=lenth+1;i<len;i++) G[i]=0;
    return;
}

多项式除法/取模

对于 F(x)G(x),求 Q(x)R(x),使得 F(x)=G(x)Q(x)+R(x)
对于一个多项式 F(x),如果我们将其反转,也就是 ai 变为 ani+1 ,记为 FR(x)
不难发现,FR(x)=xnF(1x)
由于 F(x)=G(x)Q(x)+R(x)
F(1x)=G(1x)Q(1x)+R(1x)
两侧同时乘上 xn 得:xnF(1x)=xmG(1x)xnmQ(1x)+xnm+1xm1R(1x)
所以 FR(x)=GR(x)QR(x)+xnm+1RR(x)
所以 FR(x)=GR(x)QR(x)(modxnm+1)
由于 QR(x)nm 次多项式,也就等于 FR(x)GR1(x)modxnm+1
求出 QR(x) 也就得到了 Q(x),最后做差求 R(x) 即可。
时间复杂度 O(nlogn)
代码实现:

void polymod(long long *F,int n,long long *G,int m,long long *Q,long long *R)//F=Q*G+R
{
    int len=0;
    reverse(F,F+n+1);
    reverse(G,G+m+1);
    polyinv(G,tr,n-m+1);
    for(len=1;len<=2*n-m;len<<=1);
    ntt(F,len,false),ntt(tr,len,false);
    for(int i=0;i<len;i++) tr[i]=tr[i]*F[i]%Mod;
    ntt(F,len,true),ntt(tr,len,true);
    for(int i=0;i<=n-m;i++) Q[i]=tr[i];
    reverse(Q,Q+n-m+1);
    reverse(G,G+m+1);
    reverse(F,F+n+1);
    for(len=1;len<=n;len<<=1);
    ntt(G,len,false),ntt(Q,len,false);
    for(int i=0;i<len;i++) tr[i]=G[i]*Q[i]%Mod;
    ntt(Q,len,true),ntt(tr,len,true);
    for(int i=0;i<m;i++) R[i]=chk(F[i]+Mod-tr[i]);
    return;
}

多项式多点求值

已知 F(x)m 个数,求 F(a1),F(a2)F(am)
是由秦九韶算法求显然是没有前途的,所以我们考虑其他能够求多项式点值的方法。
我们知道,将 F(x) 对一个 m 次多项式取模会得到一个 m1 次多项式,加入我们有一个 1 次多项式 G(x),记 F(x)=G(x)Q(x)+R(x),则 R(x) 应为某个常数 k
在上式中带入 x=x0,我们得到 F(x0)=G(x0)Q(x0)+k,这时,如果我们选择一个 G(x) 满足 G(x0)=0,我们就可以得到 F(x0)=k。也就是说,我们将 F(x)xx0 取模,就可以得到 F(x0)
但是求一次多项式取模是 O(nlogn) ,我们考虑如何通过分治减小复杂度。
对于问题 [l,r] ,我们先将 F(x)i=lr(xai) 取模,然后将询问分治到 [l,mid][mid+1,r] 即可,时间复杂度 T(n)=2T(n2)+O(nlogn),所以算法复杂度是 O(nlog2n)
(然而常数爆炸的我只在洛谷上过了一个点……)

转置原理处理多项式多点求值

这种时候我们就需要一种更快的方法。<---这是个链接。

多项式求ln

对于 F(x) ,求 G(x) 使得 G(x)=lnF(x)(modxn)
考虑求导和积分:
G(x)=(lnF(x))=F(x)F(x)
这也就意味着,我们只要求出在 modxn 意义下的 F(x)1F(x) 即可。
然后再把我们求出来的东西积分回去,就得到了答案 G(x)
时间复杂度 O(nlogn)
代码实现:

void polyder(long long *G,long long *F,int len)
{
	for(int i=1;i<len;i++)
		G[i-1]=i*F[i]%Mod;
	G[len-1]=0;
	return;
}
long long F[LIM],G[LIM],H[LIM];
int n,len;
int main()
{
	n=Qread();
	for(int i=0;i<n;i++)
		F[i]=Qread();
	for(len=1;len<n;len<<=1);
	polyinv(G,F,len);
	polyder(H,F,len);
	ntt(G,len<<1,false),ntt(H,len<<1,false);
	for(int i=0;i<(len<<1);i++) G[i]=G[i]*H[i]%Mod;
	ntt(G,len<<1,true);
	printf("0 ");
	for(int i=1;i<n;i++)
		printf("%lld ",G[i-1]*qpow(i,Mod-2)%Mod);
	return 0;
}

多项式exp

对于 F(x),求 G(x) 使得 G(x)=eF(x)(modxn)
因为 G(x)=eF(x)
两边取 lnlnG(x)=F(x)
移项得 lnG(x)F(x)=0
H(G(x))=lnG(x)F(x),我们需要求的就是 H(G(x)) 的零点,其中 G(x) 为变量,F(x) 为常量。
所以对其求导得 H(G(x))=1G(x)
考虑初始选择点 G0(x)=1,不断对其进行牛顿迭代: xn+1=xnf(xn)f(xn)
带入得 Gk+1(x)=Gk(x)lnGk(x)F(x)1Gk(x)=Gk(x)(1+F(x)lnGk(x))

我们来尝试证明我们能够在 O(logn) 次迭代中得到结果:
假设我们知道 G(x)=G0(x)(modxn2),我们尝试将模数推到 xn
由于 G(x)=G0(x)(modxn2),所以 H(G(x))=H(G0(x))(modxn2)
考虑 H(G(x))G0(x) 的泰勒展开:
H(G(x))=H(G0(x))+i=1H(i)(G0(x))i!(G(x)G0(x))i(modxn)
由于 G(x)=G0(x)(modxn2),所以 (G(x)G0(x))2=0(modxn)
这也就意味着,在泰勒展开式中,只有 i=1 的项是不为 0 的。
所以 H(G(x))=H(G0(x))+H(G0(x))(G(x)G0(x))(modxn)
而我们需要求的是使得 H(G(x))=0(modxn) 的项。
H(G0(x))+H(G0(x))(G(x)G0(x))=0(modxn),即 G(x)=G0(x)H(G0(x))H(G0(x))

时间复杂度 O(nlogn)
代码实现:

void polyexp(long long *F,long long *G,int lenth)
{
    G[0]=1;
    for(int len=2;len<(lenth<<1);len<<=1)
    {
        polyln(G,expg,len);
        for(int i=0;i<len;i++) expg[i]=chk(F[i]+Mod-expg[i]);
        expg[0]=chk(expg[0]+1);
        for(int i=0;i<len;i++) exf[i]=G[i];
        for(int i=len;i<(len<<1);i++) exf[i]=expg[i]=0;
        ntt(exf,len<<1,false),ntt(expg,len<<1,false);
        for(int i=0;i<(len<<1);i++) exf[i]=exf[i]*expg[i]%Mod;
        ntt(exf,len<<1,true);
        for(int i=0;i<len;i++) G[i]=exf[i];
    }
    return;
}

多项式快速幂(普通版)

对于 F(x),求 Fk(x)(modxn),保证 [x0]F(x)=1
我们知道 Fk(x)=elnFk(x)=eklnF(x),所以使用一次 lnexp 即可。
时间复杂度 O(nlogn)

多项式开根

对于 F(x),求 G(x) 满足 G2(x)=F(x)(modxn),保证 [x0]F(x)=1
法一:直接理解成 F12(x),然后直接同上 lnexp,在洛谷上大数据平均需要625ms。
法二:考虑使用牛顿迭代,我们记 H(G(x))=G2(x)F(x) ,我们需要求 H(G(x)) 的零点。
考虑 G(x)=G0(x)H(G0(x))H(G0(x))(modxn)
G(x)=G0(x)G02(x)F(x)2G0(x)(modxn)
所以 G(x)=12(G0(x)+F(x)G0(x))(modxn)
常数小于法一,在洛谷上大数据平均需要296ms。
时间复杂度 O(nlogn)
法二代码实现:

long long sqrf[LIM],sqrg[LIM];
void polysqrt(long long *F,long long *G,int lenth)
{
    G[0]=F[0];
    for(int len=2;len<(lenth<<1);len<<=1)
    {
        memset(sqrf,0,sizeof(long long)*(len<<1));
        memset(sqrg,0,sizeof(long long)*(len<<1));
        polyinv(G,sqrg,len);
        memcpy(sqrf,F,sizeof(long long)*len);
        ntt(sqrf,len<<1,false),ntt(sqrg,len<<1,false);
        for(int i=0;i<(len<<1);i++) sqrf[i]=sqrf[i]*sqrg[i]%Mod;
        ntt(sqrf,len<<1,true);
        for(int i=0;i<=len;i++) G[i]=inv2*(G[i]+sqrf[i])%Mod;
    }
    return;
}
posted @   Xun_Xiaoyao  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
/* 鼠标点击求赞文字特效 */
点击右上角即可分享
微信分享提示