您的位置:首页 > 其它

阶乘算法全集,阶乘末尾非零位,阶末尾零的个数

2015-10-27 21:02 351 查看
/阶乘各算法的 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


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可以表示成不同的阶乘和,否则不行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: