//
阶乘各算法的 C++ 类实现
#include
<
iostream
>
#include
<
cstring
>
#include
<
iomanip
>
#include
<
cmath
>
using
namespace
std;
class
Factorial {
static
const
int
MAXN
=
5001
;
//
最大阶乘数,实际用不到这么大
int
*
data[MAXN];
//
存放各个数的阶乘
int
*
nonzero;
//
从低位数起第一个非0数字
int
maxn;
//
存放最大已经计算好的n的阶乘
int
SmallFact(
int
n);
//
n <= 12的递归程序
void
TransToStr(
int
n,
int
*
s);
//
将数n倒序存入数组中
void
Multply (
int
*
A,
int
*
B,
int
*
C,
int
totallen);
//
执行两个高精度数的乘法
public
:
Factorial();
~
Factorial();
void
Calculate(
int
n);
//
调用计算阶乘
int
FirstNonZero(
int
n);
//
返回阶乘末尾第一个非0数字
int
CountZeros(
int
n);
//
返回阶乘末尾有多少个0
int
SecondNum(
int
n);
//
返回阶乘左边的第二个数字
bool
CanDivide(
int
m,
int
n);
//
判断数值 m 是否可以整除 n!
void
Output(
int
n)
const
;
};
int
Factorial::SmallFact(
int
n) {
if
(n
==
1
||
n
==
0
)
return
1
;
return
SmallFact(n
-
1
)
*
n;
}
void
Factorial::TransToStr(
int
n,
int
*
tmp) {
int
i
=
1
;
while
(n) {
tmp[i
++
]
=
n
%
10
;
n
/=
10
;
}
tmp[
0
]
=
i
-
1
;
}
void
Factorial::Multply (
int
*
A,
int
*
B,
int
*
C,
int
totallen)
{
int
i, j, len;
memset(C,
0
, totallen
*
sizeof
(
int
));
for
(i
=
1
; i
<=
A[
0
]; i
++
)
for
(j
=
1
; j
<=
B[
0
]; j
++
) {
C[i
+
j
-
1
]
+=
A[i]
*
B[j];
//
当前i+j-1位对应项 + A[i] * B[j]
C[i
+
j]
+=
C[i
+
j
-
1
]
/
10
;
//
它的后一位 + 它的商(进位)
C[i
+
j
-
1
]
%=
10
;
//
它再取余即可
}
len
=
A[
0
]
+
B[
0
];
while
(len
>
1
&&
C[len]
==
0
) len
--
;
//
获得它的实际长度
C[
0
]
=
len;
}
Factorial::Factorial() {
//
构造函数,先把<=12的阶乘计算好
maxn
=
12
; data[
0
]
=
new
int
[
2
];
data[
0
][
0
]
=
1
; data[
0
][
1
]
=
1
;
int
i, j
=
1
;
for
(i
=
1
; i
<=
12
; i
++
) {
data[i]
=
new
int
[
12
];
j
=
j
*
i;
TransToStr(j, data[i]);
}
nonzero
=
new
int
[
10
*
MAXN];
nonzero[
0
]
=
1
; nonzero[
1
]
=
1
;
//
nonzero[0]存储已经计算到的n!末尾非0数
}
Factorial::
~
Factorial() {
for
(
int
i
=
0
; i
<=
maxn; i
++
)
delete [] data[i];
delete [] nonzero;
}
void
Factorial::Calculate(
int
n) {
if
(n
>
MAXN)
return
;
if
(n
<=
maxn)
return
;
//
<= maxn的,已经在计算好的数组中了
int
i, j, len;
int
tmp[
12
];
for
(i
=
maxn
+
1
; i
<=
n; i
++
) {
TransToStr(i, tmp);
len
=
data[i
-
1
][
0
]
+
tmp[
0
]
+
1
;
data[i]
=
new
int
[len
+
1
];
Multply(data[i
-
1
], tmp, data[i], len
+
1
);
}
maxn
=
n;
}
int
Factorial::FirstNonZero(
int
n) {
if
(n
>=
10
*
MAXN) {
cout
<<
"
Super Pig, your input is so large, cannot Calculate. Sorry!
"
;
return
-
1
;
}
if
(n
<=
nonzero[
0
])
return
nonzero[n];
//
已经计算好了,直接返回
int
res[
5
][
4
]
=
{{
0
,
0
,
0
,
0
}, {
2
,
6
,
8
,
4
}, {
4
,
2
,
6
,
8
}, {
6
,
8
,
4
,
2
}, {
8
,
4
,
2
,
6
}};
int
i, five, t;
for
(i
=
nonzero[
0
]
+
1
; i
<=
n; i
++
) {
t
=
i;
while
(t
%
10
==
0
) t
/=
10
;
//
先去掉 i 末尾的 0,这是不影响的
if
(t
%
2
==
0
) {
//
t是偶数直接乘再取模10即可
nonzero[i]
=
(nonzero[i
-
1
]
*
t)
%
10
;
}
else
{
//
否则转换成 res 数组来求
five
=
0
;
while
(t
%
5
==
0
) {
if
(five
==
3
) five
=
0
;
else
five
++
;
t
/=
5
;
}
nonzero[i]
=
res[((nonzero[i
-
1
]
*
t)
%
10
)
/
2
][five];
//
(nonzero[i-1]*t)%10/2 正好序号为:1, 2, 3, 4 中的一个
}
}
nonzero[
0
]
=
n;
return
nonzero[n];
}
/*
阶乘末尾有多少个0,实际上只与5的因子数量有关,即求 n/5+n/25+n/625+......
*/
int
Factorial::CountZeros(
int
n) {
if
(n
>=
2000000000
) {
cout
<<
"
Super Pig, your input is so large, cannot Calculate. Sorry!
"
;
return
-
1
;
}
int
cnt
=
0
;
while
(n) {
n
/=
5
;
cnt
+=
n;
}
return
cnt;
}
/*
输出N!左边第二位的数字:用实数乘,超过100就除以10,最后取个位即可
*/
int
Factorial::SecondNum(
int
n) {
if
(n
<=
3
)
return
0
;
int
i;
double
x
=
6
;
for
(i
=
4
; i
<=
n; i
++
) {
x
*=
i;
while
(x
>=
100
) x
/=
10
;
}
return
(
int
(x))
%
10
;
}
bool
Factorial::CanDivide(
int
m,
int
n) {
if
(m
==
0
)
return
false
;
if
(n
>=
m)
return
true
;
int
nn, i, j, nums1, nums2;
bool
ok
=
true
;
j
=
(
int
)sqrt(
1.0
*
m);
for
(i
=
2
; i
<=
j; i
++
) {
if
(m
%
i
==
0
) {
nums1
=
0
;
//
除数m的素因子i的数量
while
(m
%
i
==
0
) {
nums1
++
;
m
/=
i;
}
nums2
=
0
; nn
=
n;
while
(nn) {
//
求 n 含有 i 因子的数量
nn
/=
i;
nums2
+=
nn;
}
if
(nums2
<
nums1) {
//
少于m中所含i的数量,则m肯定无法整除n!
ok
=
false
;
break
;
}
j
=
(
int
)sqrt(
1.0
*
m);
//
调整新的素因子前进范围
}
}
if
(
!
ok
||
m
>
n
||
m
==
0
)
return
false
;
else
return
true
;
}
void
Factorial::Output(
int
n)
const
{
if
(n
>
MAXN) {
cout
<<
"
Super Pig, your input is so large, cannot Calculate. Sorry!
"
;
return
;
}
int
i, len
=
8
;
cout
<<
setw(
4
)
<<
n
<<
"
! =
"
;
//
格式控制输出
for
(i
=
data[n][
0
]; i
>=
1
; i
--
) {
cout
<<
data[n][i];
if
(
++
len
==
58
) {
//
实际每输出50个字符就换行
len
=
8
;
cout
<<
"
"
;
}
}
if
(len
!=
8
) cout
<<
endl;
}
int
main() {
int
n, m, i;
Factorial f;
while
(cin
>>
n) {
f.Calculate(n);
f.Output(n);
cout
<<
"
该阶乘末尾第一个非0数字是:
"
<<
f.FirstNonZero(n)
<<
endl;
cout
<<
"
该阶乘总共拥有数字0的个数:
"
<<
f.CountZeros(n)
<<
endl;
cout
<<
"
该阶乘的左边的第2位数字是:
"
<<
f.SecondNum(n)
<<
endl;
cin
>>
m;
if
(f.CanDivide(m, n)) cout
<<
m
<<
"
可以整除
"
<<
n
<<
"
!
"
;
else
cout
<<
m
<<
"
不能整除
"
<<
n
<<
"
!
"
;
}
return
0
;
}
//
第2部分第(5)个算法的单独实现
#include
<
stdio.h
>
int
A[
10
]
=
{
1
,
1
,
2
,
6
,
24
,
120
,
720
,
5040
,
40320
,
362880
};
int
ans[
1024
];
int
sum;
void
insert(
int
*
a,
int
x) {
//
插入排序,插成有序表
int
i, j;
for
(i
=
1
; i
<=
a[
0
]; i
++
)
if
(a[i]
>=
x)
break
;
if
(i
<=
a[
0
]
&&
a[i]
==
x)
return
;
//
如果相等,则不用插入
if
(i
>
a[
0
]) {
a[
++
a[
0
]]
=
x;
}
else
{
for
(j
=
a[
0
]
++
; j
>=
i; j
--
)
ans[j
+
1
]
=
ans[j];
ans[i]
=
x;
}
if
(a[
0
]
==
1023
) printf(
"
Array Overflow!
"
);
}
void
search(
int
n){
for
(
int
i
=
n; i
<=
9
; i
++
){
sum
+=
A[i];
if
(sum
>
1
) insert(ans, sum);
if
(i
<
9
) search(i
+
1
);
sum
-=
A[i];
}
}
int
main(){
int
n,i;
ans[
0
]
=
1
; ans[
1
]
=
1
;
//
初始化ans数组,ans[0]表示表长
search(
0
);
//
printf("len = %d ", ans[0]);
while
(
1
){
scanf(
"
%d
"
,
&
n);
if
(n
<
0
)
break
;
if
(n
>
409114
){ printf(
"
NO
"
);
continue
;}
for
(i
=
1
; i
<=
ans[
0
]; i
++
)
if
(ans[i]
==
n) {printf(
"
YES
"
);
break
;}
else
if
(ans[i]
>
n) {printf(
"
NO
"
);
break
;}
}
return
0
;
}
//
阶乘相关算法及程序
有关阶乘的算法,不外乎两个方面:一是高精度计算;二是与数论相关。
一. 高精度计算阶乘
这实际上是最没有技术含量的问题,但是又会经常用到,所以还是得编写,优化它的计算。
首先看小于等于12的阶乘计算(计算结果不会超出32位范围):
int
factorial(
int
n) {
if
(n
==
1
||
n
==
0
)
return
1
;
return
factorial(n
-
1
)
*
n;
}
这个递归程序简单明了,非常直观,然而一旦n
>
12
,则超过32位int型的范围出现错误结果,所以上面这个递归程序仅适合n
<=
12的阶乘计算,为了计算较大n的阶乘,需要将高精度乘法算法纳入到阶乘计算中来,高精度乘法过程可以如下简单的描述:(其中A
*
B
=
C,A[
0
], B[
0
], C[
0
]分别存储长度)
for
(i
=
1
; i
<=
A[
0
]; i
++
)
for
(j
=
1
; j
<=
B[
0
]; j
++
) {
C[i
+
j
-
1
]
+=
A[i]
*
B[j];
//
当前i+j-1位对应项 + A[i] * B[j]
C[i
+
j]
+=
C[i
+
j
-
1
]
/
10
;
//
它的后一位 + 它的商(进位)
C[i
+
j
-
1
]
%=
10
;
//
它再取余即可
}
C[
0
]
=
A[
0
]
+
B[
0
];
while
(C[
0
]
>
1
&&
C[C[
0
]]
==
0
) C[
0
]
--
;
//
去头0,获得实际C的长度
有了这个高精度乘法之后,计算阶乘就可以简单的迭代进行:
for
(i
=
2
; i
<=
n; i
++
) {
将i转换成字符数组;
执行高精度乘法:将上一次结果乘上i
}
二. 与数论有关
由于阶乘到后面越来越大,巧妙的利用数论求得一些有趣的数字(数值)等成为阶乘算法的设计点,下面给出几道相关的问题与分析:
(
1
) 计算阶乘末尾第一个非0数字:
这是一个比较经典的问题,比较复杂的算法是利用一个艰难的数学公式,可惜我不会,从网上的资料学习中,整理出下面这个简单易懂的算法:
观察n
!
,可以发现在乘的过程中,对于任意 n
>
1
,n
!
的末尾第一个非0数字都是偶数。我们只需保留最后一位非零数。当要乘的数中含有因数5时,我们可以把所有的因数5都当作8来乘。这是因为:
...x2
*
5
=
...
10
(舍)或...
60
,最后一位非零数为6。而恰好2
*
8
=
16
,末位为6。
...x4
*
5
=
...
70
(舍)或...
20
,最后一位非零数为2。而恰好4
*
8
=
32
,末位为2。
...x6
*
5
=
...
30
(舍)或...
80
,最后一位非零数为8。而恰好6
*
8
=
48
,末位为8。
...x8
*
5
=
...
90
(舍)或...
40
,最后一位非零数为4。而恰好8
*
8
=
64
,末位为4。
(对于n
>
1时,最后一位不会出现
1
,
7
,
3
,
9
,而永远是2,
4
,
6
, 8的循环出现)
因此,在迭代作乘法时,主要就是计算因子5的数量,同时可见因子5的个数以4为循环节(即只需要取它的数量对4取模)。那么对于不同情况下的因子5的数量,可以通过res[
5
][
4
]
=
{{
0
,
0
,
0
,
0
}, {
2
,
6
,
8
,
4
}, {
4
,
2
,
6
,
8
}, {
6
,
8
,
4
,
2
}, {
8
,
4
,
2
,
6
}}来得到,使用nonzero[i]表示i的阶乘的最后一位,那么:
如果t是偶数,则直接乘:nonzero[i]
=
(nonzero[i
-
1
]
*
t)
%
10
。
否则nonzero[i]
=
res[((nonzero[i
-
1
]
*
t)
%
10
)
/
2
][five];
其中t是除掉所有因子5的结果,five为因子5数量对4的模。相关题目:
http:
//
acm.zju.edu.cn的第1222题。不过这一道题注意的是,它的输入n并非只在32位int数值范围内,而是有很大的长度,所以计算这道变态题目时,需要利用到高精度除法(n/=5)和高精度加法(cnt+=n)。
(
2
). 阶乘末尾有多少个0
分析发现,实际上形成末尾0,就是因子5的数量,而计算1
~
n之间包含一个因子i的个数的简单算法就是:
cnt
=
0
;
while
(n) { n
/=
i; cnt
+=
n; }
因此,直接将i换成5,就可以得到因子5的数量,也即n
!
末尾0的数量。相关题目:http:
//
acm.zju.edu.cn的第2022题。
(
3
). 返回阶乘左边的第二个数字
简单算法:用实数乘,超过100就除以10,最后取个位即可。因为整数部分的个位就是阶乘结果左边的第二个数字。相关题目:
http:
//
acm.tongji.edu.cn的1016题。
(
4
). 判断数值 m 是否可以整除 n
!
算法:使用素因子判断法
A. 首先直接输出两种特殊情况:
m
==
0
则0肯定不会整除n
!
;
n
>=
m 则m肯定可以整除n
!
;
B. 那么就只剩最后一种情况:m
>
n,我们从m的最小素因子取起,设素因子为i那么可以求得m的素因子i的个数 nums1;再检查闭区间 i
~
n 之间的数,一共包含多少个素因子i,就可以简单的利用上面(
2
)中所介绍的数学公式进行计算得到nums2。如果nums2
<
nums1,就表示1
~
n中包含素因子的数量
<
除数m包含素因子i的数量,那么m必然不能整除n
!
,置ok
=
false
。
C. 最后:如果
!
ok or m
>
n or m
==
0
则不能整除;否则可以整除
相关题目:http:
//
acm.zju.edu.cn的第1850题。
(
5
).数字N能否表示成若干个不相同的阶乘的和:
这里可以选择的阶乘为:
0
!
~
9
!
,实际上这一题与数论无关,与搜索有关。相关题目:http:
//
acm.zju.edu.cn 的2358题。
分析,由于可供选择的阶乘数量较少,直接可以利用DFS搜索来做:
A. 首先将0
~
9的阶乘作一个表A[
10
];再设置一个可以组成“和”的数组ans[N]。
B. 深度优先搜索方法:
search(n) {
for
(i
=
n; i
<=
9
; i
++
) {
sum
+=
A[i];
//
求和
如果sum在ans数组中不存在,则将sum插入到ans[]数组中
search(n
+
1
);
sum
-=
A[i];
//
回溯
}
}
C. 最后对于输入n,就在ans数组中查找是否存在n,如果存在,则表示n可以表示成不同的阶乘和,否则不行。