LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 3239|回复: 15

[算法]问一道ACM练习题

[复制链接]
发表于 2006-6-23 23:28:53 | 显示全部楼层 |阅读模式
某君曾考我的一道ACM习题,今天偶然想起,试做了一下,也不知道对不对,向大家求证一下
题目很简单:
[color="Red"]求 12345678987654321! 这个超大整数末尾有几个0
注意,是求大整数的阶乘末尾的0的个数; 如 100! 的末尾有24个0

如果你觉得数字太大,那就求一下 123454321! 的末尾有几个0吧。关键是思路。



下面是我的答案:
12345678987654321!  末尾有 3086419746913569 个0
123454321! 末尾有 30863575 个0
发表于 2006-6-24 02:50:02 | 显示全部楼层
10=2*5
由于2的出现次数绝对比5多, 因此只要考查尾数为5及0的数, 计算它们的因子中5的数量
因此写出了如下的代码
  1. #include <stdio.h>
  2. int main(void)
  3. {
  4.         unsigned long int N = 123454321;
  5.         printf("%d! has ", N);
  6.         unsigned long int n = 0;
  7.         unsigned long int i = 0;
  8.         while (N > 5) {
  9.                 n += N /= 5;
  10.         }
  11.         printf("%d zero at tail\n", n);
  12.         return 0;
  13. }
复制代码
它的计算时间
  1. rf@RemoteFish:~/src/t$ time ./a.out
  2. 123454321! has 30863575 zero at tail
  3. real    0m0.002s
  4. user    0m0.001s
  5. sys     0m0.001s
复制代码
但是它还不能回答楼主的问题, 因为它不能处理 "大数", 因此又改出了下面的版本, 利用字符串进行计算
  1. #include <stdio.h>
  2. #include <string.h>
  3. /*
  4. * s / 5
  5. * 如果结果位数减 1 则返回 1, 否则返回 0
  6. */
  7. int div(char s[], int n)
  8. {
  9.         int i = 0;
  10.         int mod = 0;
  11.         for (i = n - 1; i >= 0; i--) {
  12.                 int t = s[i] + 10 * mod;
  13.                 s[i] = t / 5;
  14.                 mod = t % 5;
  15.         }
  16.         return s[n - 1] == 0;
  17. }
  18. /* d = d + s */
  19. int add(char d[], int nd, char s[], int ns)
  20. {
  21.         int n = (nd < ns) ? nd : ns;
  22.         int i;
  23.         int x = 0;
  24.         for (i = 0; i < n; i++) {
  25.                 d[i] = d[i] + s[i] + x;
  26.                 if (d[i] >= 10) {
  27.                         d[i] -= 10;
  28.                         x = 1;
  29.                 } else x = 0;
  30.         }
  31.         if (nd > ns) {
  32.                 while (1) {
  33.                         d[i] = d[i] + x;
  34.                         if (d[i] >= 10) d[i] -= 10;
  35.                         else break;
  36.                         i++;
  37.                 }
  38.                 if (i < nd) i = nd;
  39.         } else {
  40.                 for ( ; i < ns; i++) {
  41.                         d[i] = s[i] + x;
  42.                         if (d[i] >= 10) d[i] -= 10;
  43.                         else break;
  44.                 }
  45.                 for ( ; i < ns; i++) {
  46.                         d[i] = s[i];
  47.                 }
  48.         }
  49.         return i;
  50. }
  51. int main(void)
  52. {
  53.         char buf[800] = "";
  54.         char * src = buf;
  55.         char res[100] = "";
  56.         int src_len = 0;
  57.         int res_len = 0;
  58.         int i;
  59.         memset(res, 0, sizeof(res));
  60.         printf("input the number: ");
  61.         scanf("%s", src);
  62.         src_len = strlen(src);
  63.         for (i = 0; i < (src_len + 1) / 2; i++) {
  64.                 int t = src[i] - '0';
  65.                 src[i] = src[src_len - i - 1] - '0';
  66.                 src[src_len - i - 1] = t;
  67.         }
  68.         while ((src_len -= div(src, src_len)) > 0) {
  69.                 res_len = add(res, res_len, src, src_len);
  70.         }
  71.         for (i = res_len - 1; i >= 0; i--) {
  72.                 printf("%d", res[i]);
  73.         }
  74.         printf("\n");
  75.         return 0;
  76. }
复制代码
它的计算时间
  1. rf@RemoteFish:~/src/t$ time ./a.out
  2. input the number: 12345678987654321
  3. 3086419746913569
  4. real    0m7.257s
  5. user    0m0.002s
  6. sys     0m0.000s
复制代码
其中输入数据花了一点时间, 真正的计算时间是 user 部分

写的不好, 大家见笑了
回复 支持 反对

使用道具 举报

发表于 2006-6-24 02:55:25 | 显示全部楼层
为了考查它的计算能力, 我又试了一个更大一点的数, 如下. 求证. 不知原题的数据规模及时间要求为多大
  1. rf@RemoteFish:~/src/t$ time ./a.out
  2. input the number: 1234567890987654321234567890987654321234567890987654321
  3. 308641972746913580308641972746913580308641972746913541
  4. real    0m19.097s
  5. user    0m0.001s
  6. sys     0m0.002s
复制代码
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-6-24 08:27:30 | 显示全部楼层
DoDo兄强!l
我的思路也是这样的,看因子5的数量。
原题的规模就是 12345678987654321! , 时间要求记得是0.1s, 看来根本不需要那么长呢
回复 支持 反对

使用道具 举报

发表于 2006-6-24 11:43:53 | 显示全部楼层
呵呵, 自从上了大学就再未做过这类题了, 昨天看到此题, 马上就勾起了我的兴趣, 可惜花了好久才做完, 而且还没有进行严格的测试. 看来脑子长时间不用果然就生锈了

本版块虽然人气不旺, 但偶有帖子却大都是值得仔细研究和思考的.
毕竟一个论坛的价值不是只在于人气的, 呵呵
回复 支持 反对

使用道具 举报

发表于 2006-6-24 17:30:14 | 显示全部楼层
算法一:

#include <stdio.h>
#include <stdlib.h>

/*题目:求n!中末尾含有多少个0


*提示:
*采用从1乘到n,每乘一个数判断一次,若后面有0则去掉后面的0,并记下去掉的0的个数。
*我们是求后面的0的个数,与前面一些数无关,为了不超出数的表示范围,去掉前面与0无
*关的数,只保留2位有效数,当乘完n次后就得到0的个数。
*/

int main()
{
  int n,i,product=1,count=0;
  printf("请输入n!算式中n的值:");
  scanf("%d",&n);
  
  for(i=1;i<=n;i++){
      product*=i;                    //1*2*3……*n
      while(product%10==0){
            count++;                 //计算0的个数
            product/=10;           
      }                              //这里会遇到有多个0的情况,比如:24*25 。
      product%=100;
  }
  printf("结果有%d个0。\n",count);
  
  system("AUSE");   
  return 0;
}
//////////////////////////////////////////////////////////////////
算法二:
#include <stdio.h>
#include <stdlib.h>

/*题目:求n!中末尾含有多少个0


*提示:
*第一种方法能得到正确的结果,可是运行的次数太多了,
*在1×2×3×...×n这些数中有些与生成0无关的数(如3、7、9、13等)可以不考虑进去,
*其实影响生成0的数只有2的倍数和5的倍数,如在1到9这些数中只有2×5,4×5,6×5,8×5,这些数能生成0,
*从这些数中我们还可以看出,真正影响生成0的是5的倍数,这些数的分解数含2的数显然多于含5的数,
*因此我们可以得出这样一个结论:n!的分解数中有多少个5,末尾就有多少个0。
*这样就可以使外面的大循环的次数减少五分之四。
*/

int main()
{
  int n,i,num,count=0;
  printf("请输入n!算式中n的值:");
  scanf("%d",&n);
  
  for(i=5;i<=n;i=i+5){
      count++;               
      num=i/5;                  
      while(num%5==0){
            count++;
            num/=5;
      }                          //处理5*5的数
  }
  
  printf("结果有%d个0。\n",count);
  
  system("AUSE");   
  return 0;
}
//////////////////////////////////////////////////////////////////
算法三:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/*题目:求n!中末尾含有多少个0


*提示:
*第二种方法是把含5的数转变成含0的数,再求0的个数,
*我们已经知道n!的分解数中有多少个5就有多少个0,我们可不可以直接求5的个数呢?答案是肯定的。
*这种方法就是根据含5的个数求0的个数,用层层剥皮法。
*先以5为步长,执行一次循环,进行第一次剥皮,求出含5的个数(个数为n/5取整),
*通过第一次剥皮5,10,15,20,25,30...变成1,2,3,4,5,6...
*再以5 2为步长,执行第二次循环,进行第二次剥皮,求出含5 2的个数(个数为n/25取整),
*在第一次剥皮后25,50,75,100,125,150……这些数变成5,10,15,20,25,30……
*再经过第二次剥皮就变成1,2,3,4,5,6……再以5 3为步长……直到步长大于或等于n退出循环。
*/

int main()
{
  int n,i,step,count=0;
  printf("请输入n!算式中n的值:");
  scanf("%d",&n);
  
  for(step=1;step<=n;step++){
      if(pow(5,step)>n)break;
      for(i=pow(5,step);i<=n;i=i+pow(5,step)){
            count++;
      }
  }
  
  printf("结果有%d个0。\n",count);
  
  system("AUSE");   
  return 0;
}
回复 支持 反对

使用道具 举报

发表于 2006-6-24 19:55:31 | 显示全部楼层
stylev 兄的思想是正确的, 但是算法的优化还不足, 而且以 C 固有数据类型是无法保存题目中的大数及其结果的

另外, stylev 兄是用 Dev-Cpp 的吧
回复 支持 反对

使用道具 举报

发表于 2006-6-25 02:22:50 | 显示全部楼层
昨晚做了一下,由于考虑不周全,先删之,以免误导大家
回复 支持 反对

使用道具 举报

发表于 2006-6-25 02:24:14 | 显示全部楼层
  1. #include <stdio.h>
  2. #include <math.h>
  3. int main(void)
  4. {
  5. unsigned char sdata[800];
  6. long data[100]={0};
  7. int str_len=0,dat_len=0,sum_len;
  8. int i,j;
  9. printf("Please enter the data:");
  10. scanf("%s",&sdata);
  11. printf("\n");
  12. str_len=strlen(sdata);
  13. dat_len=((str_len/8)*8==str_len)?(str_len/8):(str_len/8+1);
  14. for(i=0;i<dat_len;i++)
  15.         {int tmp;
  16.         tmp=str_len-i*8-1;
  17.         for(j=0;j<8;j++)
  18.                 {if(tmp-j<0) break;
  19.                 data[i]+=(sdata[tmp-j]-'0')*pow(10,j);
  20.                 }
  21.         }
  22. long sum[100]={0};
  23. while(dat_len>0)
  24.         {dat_len=div(data,dat_len);
  25.         add(data,sum,dat_len);
  26.         }
  27. sum_len=1;
  28. for(i=99;i>0;i--)
  29.         if(sum[i]>0)
  30.                 {sum_len=i+1;
  31.                 break;
  32.                 }
  33. printf("There are ");
  34. printarray(sum,sum_len);
  35. printf(" zeros at the end\n");
  36. return 1;
  37. }
  38. void add(long data[], long sum[],int dat_len)
  39. {
  40. int i;
  41. for(i=0;i<dat_len;i++)
  42.         {int tmp1,tmp2;
  43.         tmp1=data[i]+sum[i];
  44.         tmp2=tmp1/100000000;
  45.         sum[i+1]+=tmp2;
  46.         sum[i]=tmp1-tmp2*100000000;
  47.         }
  48. }
  49. int div(long data[],int dat_len)
  50. {int i;
  51. for(i=dat_len-1;i>0;i--)
  52.         {int tmp;
  53.         tmp=data[i]/5;
  54.         data[i-1]+=(data[i]-tmp*5)*100000000;
  55.         data[i]=tmp;
  56.         }
  57. data[0]=data[0]/5;
  58. if(data[dat_len-1]==0) return (dat_len-1);
  59. else
  60. return dat_len;
  61. }
  62. void printarray(long data[],int dat_len)
  63. {if(dat_len==1) printf("%ld",data[0]);
  64. else
  65.         {printf("%ld",data[dat_len-1]);
  66.         int i;
  67.         for(i=dat_len-2;i>=0;i--) printf("%08ld",data[i]);
  68.         }
  69. }
复制代码

  1. [earth@localhost programming]$ gcc -o acm2 acm2.c -lm
  2. acm2.c: 在函数 ‘main’ 中:
  3. acm2.c:13: 警告:隐式声明与内建函数 ‘strlen’ 不兼容
  4. acm2.c: 在顶层:
  5. acm2.c:40: 警告:与 ‘add’ 类型冲突
  6. acm2.c:26: 警告:‘add’ 的上一个隐式声明在此
  7. acm2.c:66: 警告:与 ‘printarray’ 类型冲突
  8. acm2.c:35: 警告:‘printarray’ 的上一个隐式声明在此
  9. [earth@localhost programming]$ time ./acm2
  10. Please enter the data:12345678987654321
  11. There are 3086419746913569 zeros at the end
  12. real    0m8.403s
  13. user    0m0.000s
  14. sys     0m0.000s
  15. [earth@localhost programming]$ time ./acm2
  16. Please enter the data:123456789009998776662134871264891249126419236417937187171972318919619834796378197391274211341481842189318528319461783468129743913193741294713412487186518932
  17. There are 30864197252499694165533717816222812281604809104484296792993079729904958699094549347818552835370460547329632079865445867032435978298435323678353121796629625 zeros at the end
  18. real    0m18.337s
  19. user    0m0.000s
  20. sys     0m0.000s
复制代码
回复 支持 反对

使用道具 举报

发表于 2006-7-15 16:05:42 | 显示全部楼层
其实只要实现了长整数运算,这题满简单的

随手写的scheme代码:

  1. (define (num-of-zero n)
  2.   (let ((num (floor (/ n 5))))
  3.   (if (< num 1)
  4.       0
  5.       (+ num (num-of-zero num)))))
复制代码
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表