算法——阶乘之和(数据溢出以及时间测试)

来源:互联网 发布:自动接听电话软件 编辑:程序博客网 时间:2024/06/18 10:08

阶乘之和


例题:

输入n,计算S = 1!+2!+3!+...+n!的未6位(不含前导0)。n<=10^6,n!表示前n个正整数之积。


样例输入:

10


样例输出:

37913


分析:

这个任务看似不难,实际却有陷阱。先看如下代码:


代码1(有缺陷):

//abc
#include<stdio.h>#include<stdlib.h>int main(){unsigned long int n,S = 0;scanf("%ld",&n);for(int i=1;i<=n;i++){int long factorial = 1;for(int j=1;j <=i;j++)factorial *= j;S += factorial;}printf("%ld\n",S % 1000000);return 0;}


#include<stdio.h>#include<time.h>int main(){const int MOD = 1000000;int n,S = 0;scanf("%d",&n);for(int i=1;i<=n;i++){int factorial = 1;for(int j=1;j<=i;j++)factorial = (factorial * j % MOD);S = (S + factorial) % MOD;}printf("%d\n",S);printf("Time used = %.2f\n",(double)clock() / CLOCKS_PER_SEC);return 0; } 
首先是算法改进:对每部阶乘进行求余,随后的结果S再进行求余。先看测试结果:

输入20,输出结果:820313; 输入40,输出结果:940313; 输入100,输出:940313;

输入160,输出结果:940313; 输入1600,输出结果:940313..........


可以发现从40开始,答案始终不变。这是因为当25!末尾有6个0,所以从第5项开始,后面的所有项都不会影响和的末6位数字。

什么意思呢:1!-10!举例如下:

1~10的阶乘如下:1!=12!=23!=64!=245!=1206!=7207!=50408!=40320 9!=362880 10!=3628800
所以有此可见。。。。所以只需要在程序的最前面加一条语句“if(n>25) n=25;”,效率和溢出将都不存在问题。


下面来看程序中其他的东西:这个程序的真正特别之处在于计时函数clock()的使用。该函数返回程序目前为止运行的时间。

这样,在程序结束之前调用此函数,便可获得整个程序的运行时间。这个时间除以常熟CLOCKS_PER_SEC之后得到的值以“秒”为单位。

C语言函数clock()  功 能:

返回处理器调用某个进程或函数所花费的时间。
  用 法: clock_t clock(void);
  说明:clock_t其实就是long,即长整形。该函数返回值是硬件滴答数,要换算成秒或者毫秒,需要除以CLK_TCK或者 CLK_TCK
CLOCKS_PER_SEC。比如,在VC++6.0下,这两个量的值都是1000,这表示硬件滴答1000下是1秒,因此要计算一个进程的时间,用clock()除以1000即可。


输入“20”,按Enter键后,系统瞬间给出了答案820313。但是,输出的Time used居然不是0!其原因在于,键盘输入的时间也被使计算在内——这的确

是程序启动之后才进行的。为了避免输入数据的时间影响测试结果,可以使用一种称为“管道”的小技巧;在windows命令行下执行echo 20|abc,操作系统

会自动把20输入,其中abc是程序名。


测试可知,程序的运行时间和n的平方成正比。(即程序的时间复杂度为O(n^2))).



我们来测试一下这个程序(测试环境为Dev-c++):


当输入10时,输出37913,结果正确。 但当输入100时,输出-961703。
我们知道int最大值为2^31-1,没错,在乘法累加的过程中,整形S的数据溢出。因为我们只求的是阶乘之和
的最后六位,没必要求出阶乘S的结果,所以可以从如下进行改进(过求出S的结果,难度更大)。

想要解决这个问题,需要运用这个数学知识:
只要计算包含加法,减法和乘法的整数表达式除以正整数n的余数,可以在每部计算之后对n取余,结果不变。
下面请看改进过后的代码2,还会有新的东西:


#include<stdio.h>#include<time.h>int main(){const int MOD = 1000000;int n,S = 0;scanf("%d",&n);for(int i=1;i<=n;i++){int factorial = 1;for(int j=1;j<=i;j++)factorial = (factorial * j % MOD);S = (S + factorial) % MOD;}printf("%d\n",S);printf("Time used = %.2f\n",(double)clock() / CLOCKS_PER_SEC);return 0; } 

首先是算法改进:对每部阶乘进行求余,随后的结果S再进行求余。

先看测试结果:
输入20,输出结果:820313; 输入40,输出结果:940313; 输入100,输出:940313;
输入160,输出结果:940313; 输入1600,输出结果:940313..........


可以发现从40开始,答案始终不变。这是因为当25!末尾有6个0,所以从第5项开始,后面的所有项都不会影响和的末6位数字。


什么意思呢:1!-10!举例如下:
1~10的阶乘如下:
1!=1
2!=2
3!=6
4!=24
5!=120
6!=720
7!=5040
8!=40320 
9!=362880 
10!=3628800


所以有此可见。。。。所以只需要在程序的最前面加一条语句“if(n>25) n=25;”,效率和溢出将都不存在问题。


下面来看程序中其他的东西:这个程序的真正特别之处在于计时函数clock()的使用。该函数返回程序目前为止运行的时间。
这样,在程序结束之前调用此函数,便可获得整个程序的运行时间。这个时间除以常熟CLOCKS_PER_SEC之后得到的值以“秒”为单位。


(C语言函数clock()  功 能:
返回处理器调用某个进程或函数所花费的时间。
  用 法: clock_t clock(void);
  说明:clock_t其实就是long,即长整形。该函数返回值是硬件滴答数,要换算成秒或者毫秒,需要除以CLK_TCK或者 CLK_TCK
CLOCKS_PER_SEC。比如,在VC++6.0下,这两个量的值都是1000,这表示硬件滴答1000下是1秒,因此要计算一个进程的时间,用clock()除以1000即可。)


输入“20”,按Enter键后,系统瞬间给出了答案820313。但是,输出的Time used居然不是0!其原因在于,键盘输入的时间也被使计算在内——这的确
是程序启动之后才进行的。为了避免输入数据的时间影响测试结果,可以使用一种称为“管道”的小技巧;在windows命令行下执行echo 20|abc,操作系统
会自动把20输入,其中abc是程序名。


测试可知,程序的运行时间和n的平方成正比。(即程序的时间复杂度为O(n^2))).



0 0
原创粉丝点击