关于阶乘加速

最近开始思考起了阶乘加速的问题,深入思考了一下,得到了一些有趣的想法。

尝试1:分治

既然阶乘最朴素的算法都是 O ( n ) O(n) O(n)的,那么最直接的想法就是把 n n n踢掉,在塞个 log ⁡ n \log{n} logn之类进去,自然就是分治了。

想法1

第一个想法是每次从中间切开,分成 1 × 2 × 3 × . . . × n 2 1\times2\times3\times...\times \frac{n}{2} 1×2×3×...×2n ( n 2 + 1 ) × ( n 2 + 2 ) × ( n 2 + 3 ) × . . . × n (\frac{n}{2}+1)\times(\frac{n}{2}+2)\times(\frac{n}{2}+3)\times...\times n (2n+1)×(2n+2)×(2n+3)×...×n 两部分,然后递归求解。
但这样,子问题的性质好像发生了改变,虽然它俩都是求区间积,但一个是从1开始,而一个是从 n 2 + 1 \frac{n}{2}+1 2n+1开始,并非求阶乘,就让人很难受,所以我立马放弃了这个想法。

其实是我想半天也没想出怎么处理子问题

想法2

第二个想法差点让我相信它就是我想要的答案了,同样是分成两部分,FFT给了我一点启发,这次我把奇数和偶数分成两组: 1 × 3 × 5 × 7 × . . . 1\times3\times5\times7\times... 1×3×5×7×... 2 × 4 × 6 × 8 × . . . 2\times4\times6\times8\times... 2×4×6×8×...

先单看偶数部分,你就会发现这太让人高兴了,每个数字提个 2 2 2出来就变成了 2 m ( 1 × 2 × 3 × 4 × . . . ) 2^m(1\times2\times3\times4\times...) 2m(1×2×3×4×...),后半部分又可以递归分解,但坏就坏在奇数部分,这简直折磨死人,我尝试了各种办法想把它处理 (比如把奇数表示成偶数 + 1 +1 +1),可十分不幸,这些方法都不可行,于是我十分甚至九分不舍地放弃了这个想法。

尝试二:素数

脑子里突然闪出一个想法。

根据唯一分解定理:
n = p 1 e 1 p 2 e 2 p 3 e 3 . . . p k e k n=p_1^{e_1}p_2^{e_2}p_3^{e_3}...p_k^{e_k} n=p1e1p2e2p3e3...pkek
其中 p i p_i pi为素数, e i e_i ei为质数 p i p_i pi的指数。
那么 n × m n\times m n×m不就是两数分解后对应素数指数相加吗?举个例子,假设
n = 2 3 × 3 2 × 5 1 × 7 1 m = 2 2 × 3 1 × 5 2 × 7 1 × 11 \begin{aligned} n&=2^3\times3^2\times5^1\times7^1\\ m&=2^2\times3^1\times5^2\times7^1\times11 \end{aligned} nm=23×32×51×71=22×31×52×71×11
那么
n × m = 2 5 × 3 3 × 5 3 × 7 2 × 1 1 1 = 2 3 + 2 × 3 2 + 1 × 5 1 + 2 × 7 1 + 1 × 1 1 1 \begin{aligned} n\times m&=2^5\times3^3\times5^3\times7^2\times11^1\\ &=2^{3+2}\times3^{2+1}\times5^{1+2}\times7^{1+1}\times11^1 \end{aligned} n×m=25×33×53×72×111=23+2×32+1×51+2×71+1×111
也就是说,即使是 1 × 2 × 3 × . . . × n 1\times2\times3\times...\times n 1×2×3×...×n,分解出来也不过是一堆素数,每个素数头上顶个指数罢了!那么这时就有想法了,只要通过某种手段获取到每一个素数的指数,就能使用快速幂来求出素数的对应次方,最后累乘起来就是答案,如果 π ( n ) \pi(n) π(n)表示 [ 1 , n ] [1,n] [1,n]中的素数个数,那么时间代价就是 O ( ∑ i = 1 π ( n ) log ⁡ log ⁡ p i n ! ) O(\sum_{i=1}^{\pi(n)}\log{\log_{pi}n!}) O(i=1π(n)loglogpin!)

接下来构造所谓“某种手段”,最后,想到了一种 O ( ∑ i = 1 π ( n ) log ⁡ p i n ) O(\sum_{i=1}^{\pi(n)}\log_{p_i}{n}) O(i=1π(n)logpin) 的方法,方法很简单,我们知道,在 1 , 2 , 3... n 1,2,3...n 1,2,3...n这个序列中,从 1 1 1开始,每隔 ( p i − 1 ) (p_i-1) (pi1)个数字就有一个能整除 p i p_i pi的数字。假设 p i = 3 p_i=3 pi=3,在 1 , 2 , 3... n 1,2,3...n 1,2,3...n中, 3 , 6 , 9... 3,6,9... 3,6,9...都能被 3 3 3整除,总共 ⌊ n 3 ⌋ \lfloor{\frac{n}{3}} \rfloor 3n个数字,把 ⌊ n 3 ⌋ \lfloor{\frac{n}{3}} \rfloor 3n加到 3 3 3对应的指数 e e e中,那么 3 , 6 , 9... 3,6,9... 3,6,9...这些数字中,有 ⌊ ⌊ n 3 ⌋ 3 ⌋ \lfloor \frac{\lfloor{\frac{n}{3}} \rfloor}{3} \rfloor 33n个能被 3 2 3^2 32整除,再把 ⌊ ⌊ n 3 ⌋ 3 ⌋ \lfloor \frac{\lfloor{\frac{n}{3}} \rfloor}{3} \rfloor 33n加到 e e e中,以此类推,我们只需要不断将 n n n除以 p i p_i pi,将每次除得的数字加到 e e e中,直到 n < p i n{<}p_i n<pi为止,这样就能得到 p i p_i pi的指数 e e e了,加上前面所推,总时间复杂度为
O ( ∑ i = 1 π ( n ) log ⁡ p i n + log ⁡ log ⁡ p i n ! ) O(\sum_{i=1}^{\pi(n)}\log_{p_i}{n}+\log{\log_{pi}n!}) O(i=1π(n)logpin+loglogpin!)

但其实这有个缺陷,那就是必须先求出 [ 1 , n ] [1,n] [1,n]中的所有素数,这个代价是 O ( n ) O(n) O(n),但转念一想现实中都是机器先打好素数表,然后再用,所以这个缺陷也不是很大。

const int N=1001;
int n;
vector<int>primes;
//int primes[N]={2,3,5,7......} 
bool vis[N];
void get_primes(int n){
	for(int i=2;i<=n;i++){
		if(vis[i])continue;
		primes.push_back(i);
		for(int j=i+i;j<=n;j+=i){
			vis[j]=1;
		}
	}
}

long long int fast_pow(int a,int x){
	if(x==0)return 1;
	if(x==1)return a;
	long long int p=pow(a,x>>1);
	return p*p*(x%2?a:1);
}

long long int fast_fac(int n){
	long long int ans=1;
	for(int i=0;i<primes.size();i++){
		long long int e=0,p=primes[i],x=n;
		while(x>=p)e+=(x/=p);
		ans*=fast_pow(p,e);
	}return ans;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值