阶乘取模
阶乘取模
给定素数p,以及某个数0≤n<p,要求计算n!,其中p≤109+7。
讲两种做法,第一种时间复杂度为O(√n(log2n)2),第二种时间复杂度为O(√nlog2n)。
我们记fd(x)=∏di=1x+i。我们记m=⌈√n⌉,那么我们只需要计算出fm(0),fm(m),…,fm((m−1)m),并将它们乘起来,缺失的部分最多O(m)项,我们暴力处理即可。
那么怎么计算fm(km)呢,其中0≤k<m。我们可以发现fm是一个m阶的多项式,我们可以先求出通过分治+FFT以O(m(log2m)2)时间复杂度快速求出多项式,之后利用多点求值的技术O(m(log2m)2)的时间复杂度内求出这m个值。
但是众所周知,这个多点求值的算法常数是真的大,假如你真的用这个方法,那很有可能会TLE的。下面我们提第二种方法。
我们考虑倍增的方法,假设我们已知:fd(ks),其中0≤k≤d。下面我们考虑怎么计算fd+1(ks)以及f2d(ks)。
可以发现fd+1(ks)=fd(ks)⋅(ks+d+1),而对于fd+1((d+1)s),我们可以直接暴力计算,时间复杂度最多O(√n)。
而对于f2d(ks),我们可以通过f2d(ks)=fd(ks)fd(ks+d)求解。右边项我们需要的是
- fd(ks),0≤k≤2d
- fd(ks+d),0≤k≤2d
记g(x)=fd(xs),可以发现问题实际上是给定了g(0),…,g(d),要求插值出g(k+δ),其中0≤k≤d。考虑通过拉格朗日插值法求解g。
g(x)=d∑i=0g(i)d∏j=0,j≠ix−ji−j由于我们仅利用上面公式求解未知的量,因此一定有x≠i。我们可以做下面变换:
=d∑i=0g(i)∏dj=0x−j(x−i)∏j≠ii−j=[d∏j=0x−j][d∑i=01(x−i)g(i)∏dj=0,j≠ii−j]下面我们考虑如何计算g(t),其中左边的项可以通过O(d)时间复杂度预处理阶乘,之后每个请求O(1)处理。而对于右边的部分,可以发现它可以描述为两个多项式的卷积,我们提前通过FFT处理出卷积的结果即可,预处理的时间复杂度为O(dlog2d),回答单个请求的时间复杂度为O(1)。
这里提一下上面出现了比较多的x−i,我们可以保证它不可能是0。因为我们可以保证所有要求的g(k+δ)都是未知量,即和0,1,…,d无交集。
上面的过程总的时间复杂度为O(1log21+2log22+…+mlog2m)=O(mlog2m)=O(√nlog2n)