最近开始思考起了阶乘加速的问题,深入思考了一下,得到了一些有趣的想法。
尝试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)
(pi−1)个数字就有一个能整除
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
⌊3⌊3n⌋⌋个能被
3
2
3^2
32整除,再把
⌊
⌊
n
3
⌋
3
⌋
\lfloor \frac{\lfloor{\frac{n}{3}} \rfloor}{3} \rfloor
⌊3⌊3n⌋⌋加到
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;
}