多项式算法的常数问题

本文探讨了多项式算法中的常数问题,通过举例分析了FFT、NTT、FWT、牛顿迭代等算法的常数影响,强调在实际应用中常数的重要性。例如,多项式多点求值在n=64000时,即使时间复杂度为O(nlog2n),也可能因常数过大导致超时。文章提醒读者在选择算法时要注意常数因素。
摘要由CSDN通过智能技术生成

引言

最近几天自学了多项式全家桶。

众所周知,多项式算法的常数都比较巨大。比如多项式多点求值, n = 64000 n=64000 n=64000,时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n),时限竟然开到了3000ms,导致直接暴力卡卡常都能水过去。(卡常卡的好累)

WC2019讲课人JOHNKRAM曾经说过,多项式的题目,只需要让暴力跑不过去就够了。

这一篇文章就主要讲述一下多项式算法的常数问题。

因为多项式全家桶已(wo)经(shi)在(zai)日(lan)报(de)里(xie)了,这篇文章几乎不会提及多项式算法的推导过程,所以……不会多项式全家桶的同学请参考这篇洛谷日报

先贴一下我的多项式全家桶代码

FFT、NTT、FWT

这三种变换可以说是最基础的多项式算法了。

题目链接:FFTFWT

void FFT(complex*A,int type)
{
   
	for(int i=0;i<limit;i++)
	  if(i<r[i])swap(A[i],A[r[i]]);
	for(int mid=1;mid<limit;mid<<=1)
	{
   
		complex Wn(cos(Pi/mid),type*sin(Pi/mid));
		for(int R=mid<<1,j=0;j<limit;j+=R)
		{
   
			complex w(1,0);
			for(int k=0;k<mid;k++,w=w*Wn)
			{
   
				complex x=A[j+k],y=w*A[j+mid+k];
				A[j+k]=x+y;
				A[j+mid+k]=x-y;
			}
		}
	}
	if(type==-1)
	{
   
		for(int i=0;i<limit;i++)
		  A[i]=A[i]/limit;
	}
}
ll omega[2][21][1<<20];
void pre()//预处理单位根,可以显著优化NTT常数
{
   
	for(int mid=1,i=0;i<=20;mid<<=1,i++)
	{
   
		ll w1=quick_pow(3,(MOD-1)/(mid<<1));
		ll w2=quick_pow(3,MOD-1-(MOD-1)/(mid<<1));
		omega[0][i][0]=omega[1][i][0]=1;
		for(int k=1;k<mid;k++)
		{
   
			omega[1][i][k]=omega[1][i][k-1]*w1%MOD;
			omega[0][i][k]=omega[0][i][k-1]*w2%MOD;
		}
	}
}
void NTT(ll*A,int type)
{
   
	for(int i=0;i<limit;i++)
	  if(i<r[i])swap(A[i],A[r[i]]);
	if(type==-1)type=0;
	for(int mid=1,i=0;mid<limit;mid<<=1,i++)
	{
   
		for(int R=mid<<1,j=0;j<limit;j+=R)
		{
   
			for(int k=0;k<mid;k++)
			{
   
				ll x=A[j+k]%MOD,y=omega[type][i][k]*A[j+mid+k]%MOD;
				A[j+k]=x+y;
				A[j+mid+k]=x-y;
			}
		}
	}
	for(int i=0;i<limit;i++)
	{
   
		A[i]%=MOD;
		if(A[i]<0)A[i]+=MOD;
	}
	if(type==0)
	{
   
		ll inv=quick_pow(limit,MOD-2);
		for(int i=0;i<limit;i++)A[i]=A[i]*inv%MOD;
	}
}
void FWT_or(ll*A,int type)
{
   
	for(int mid=1;mid<limit;mid<<=1)
	  for(int R=mid<<1,j=0;j<limit;j+=R)
		for(int k=0;k<mid;k++)
		  if(type==1)A[j+mid+k]+=A[j+k];
		  else A[j+mid+k]-=A[j+k];
	for(int i=0;i<limit;i++)
	{
   
		A[i]%=MOD;
		if(A[i]<0)A[i]+=MOD;
	}
}
void FWT_and(ll*A,int type)
{
   
	for(int mid=1;mid<limit;mid<<=1)
	  for(int R=mid<<1,j=0;j<limit;j+=R)
		for(int k=0;k<mid;k++)
		  if(type==1)A[j+k]+=A[j+mid+k];
		  else A[j+k]-=A[j+mid+k];
	for(int i=0;i<limit;i++)
	{
   
		A[i]%=MOD;
		if(A[i]<0)A[i]+=MOD;
	}
}
void FWT_xor(ll*A,int type)
{
   
	for(int mid=1;mid<limit;mid<<=1)
	  for(int R=mid<<1,j=0;j<limit;j+=R)
		for(int k=0;k<mid;k++)
		{
   
			ll x=A[j+k],y=A[j+mid+k];
			A[j+k]=x+y;
			A[j+mid+k]=x-y;
			if(A[j+k]>MOD)A[j+k]-=MOD;
			if(A[j+mid+k]<0)A[j+mid+k]+=MOD;
			if(type==-1)
			{
   
				A[j+k]=A[j+k]*inv2%MOD;
				A[j+mid+k]=A[j+mid+k]*inv2%MOD;
			}
		}
}

为了方便,我们假设这三种变换的常数都是 1 1 1,也就是 n log ⁡ n n\log n nlogn。(当然这些算法的常数也是有的,而且还不小)

任意模数FFT(MTT)

题目链接

共轭优化后,其实本质上就是4次DFT啦:(目前这份代码无论是时间上还是空间上排名都十分靠前,请自动忽略指令集优化的FFT)

void Mul(ll*A,ll*B,ll*C,ll MOD,complex*a,complex*b)//4nlogn
{
   
	complex da1,db1,dc1,dd1,da2,db2,dc2,dd2,Da1,Db1,Dc1,Dd1,Da2,Db2,Dc2,Dd2;
	for(int i=0;i<limit;i++)a[i]=complex(A[i]&M,A[i]>>15);
	for(int i=0;i<limit;i++)b[i]=complex(B[i]&M,B[i]>>15);
	FFT(a,1);
	FFT(b,1);
	for(int i=0;i<=(limit>>1);i++)
	{
   
		int j=(limit-i)&(limit-1);
		da1=(a[i]+a[j].conj())*complex(0.5,0);
		db1=(a[i]-a[j].conj())*complex(0,-0.5);
		dc1=(b[i]+b[j].conj())*complex(0.5,0);
		dd1=(b[i]-b[j].conj())*complex(0,-0.5);
		da2=(a[j]+a[i].conj())*complex(0.5,0);
		db2=(a[j]-a[i].conj())*complex(0,-0.5);
		dc2=(b[j]+b[i].conj())*complex(0.5,0);
		dd2=(b[j]-b[i].conj())*complex(0,-0.5);
		Da1=da1*dc1;
		Db1=da1*dd1;
		Dc1=db1*dc1;
		Dd1=db1*dd1;
		Da2=da2*dc2;
		Db2=da2*dd2;
		Dc2=db2*dc2;
		Dd2=db2*dd2;
		a[i]=Da2+Db2*complex(0,1.0);
		b[i]=Dc2+Dd2*complex(0,1.0);
		a[j]=Da1+Db1*complex(0,1.0);
		b[j]=Dc1+Dd1*complex(0,1.0);
	}
	FFT(a,1);
	FFT(b,1);
	for(int i=0;i<limit;i++)
	{
   
		ll da=(ll)(a[i].x/limit+0.5)%MOD;
		ll db=(ll)(a[i].y/limit+0.5)%MOD;
		ll dc=(ll)(b[i].x/limit+0.5)%MOD;
		ll dd=(ll)(b[i].y/limit+0.5)%MOD;
		C[i]=(da+((ll)(db+dc)<<15)+((ll)dd<<30))%MOD;
		if(C[i]<0)C[i]+=MOD;
	}
}

T ( n ) = 4 n log ⁡ n T(n)=4n\log n T(n)=4nlogn

牛顿迭代

牛顿迭代计算复杂度的公式是

T ( n ) = T ( n 2 ) + O ( n log ⁡ n ) = O ( n log ⁡ n ) T(n)=T\left(n\over 2\right)+O(n\log n)=O(n\log n) T(n)=T(2n)+O(nlogn)=O(nlogn)

让我们认真化一下计算复杂度的式子(我们先假设后面那个 O ( n log ⁡ n ) O(n\log n) O(nlogn)的常数为 1 1 1,所有的 log ⁡ \log log都是以 2 2 2为底):

T ( n ) = T ( n 2 ) + n log ⁡ n T(n)=T\left(n\over 2\right)+n\log n T(n)=T(2n)+nlogn

= n log ⁡ n + n 2 log ⁡ ( n 2 ) + T ( n 4 ) =n\log n+\dfrac n2 \log\left(\dfrac n2\right)+T\left(n\over 4\right) =nlogn+

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值