免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 7698 | 回复: 17
打印 上一主题 下一主题

阶乘的最右一位非零数字求和 [复制链接]

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-05-15 19:57 |只看该作者 |倒序浏览
Problem
对于小于25000的自然数n,求阶乘n!,(n-1)!,(n-2)!...3!,2!,1!最右边的非零数之和。

例如:

当n=5时,
5!=120,右边非零数为2;
4!=24,右边非零数为4;
3!=6,右边非零数为6;
2!=2,右边非零数为2;
1!=1,右边非零数为1。
其右边的非零数之和为15。

Input
本题有多组数据,每组数据包含一个正整数N(N不大于25000)占一行。

Output
对给定的每组输入数据,输出一个整数。每个结果占一行。不要输出额外的空行。

Sample Input
5
10
1

Sample Output
15
39
1

部份结果如下:
11
47

111
543

1111
5547

11111
55567

111111
555455

1111111
5555481

11111111
55555817

111111111
555555081

1111111111
5555555731

11111111111
55555555603

111111111111
555555554817

1111111111111
5555555553031

11111111111111
55555555553495

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
2 [报告]
发表于 2006-05-15 20:00 |只看该作者
容易得到O(nlog(n))的算法:
  1. int fac2[] = {6,2,4,8};
  2. int fac[] = {1,1,2,6,4};

  3. long long calc(long long n)
  4. {
  5.     long long s = 1, i, t;
  6.     int v=1, k=0, l=0;
  7.     for(i=2; i<=n; i++) {
  8.         for(t=i; t%2==0; t/=2) k++;
  9.         for(;t%5==0; t/=5) l++;
  10.         v = ((t%10) * v) % 10;
  11.         s += (v * fac2[(k-l)%4]) % 10;
  12.     }
  13.     return s;
  14. }

  15. int main()
  16. {
  17.     long long n;
  18.     printf("input n:");
  19.     scanf("%lld", &n);
  20.     printf("result:%lld\n", calc(n));
  21. }
复制代码

但问题是如何得到O(log(n))的算法
很有趣的题目,花了我整整两天~~~

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
3 [报告]
发表于 2006-05-15 20:01 |只看该作者
O(log(n))的算法过会儿再给出
容我卖个关子啊~

论坛徽章:
0
4 [报告]
发表于 2006-05-15 21:02 |只看该作者
x=2^l 5^n *y

(l, n, y mod 10)
2^l mod 10: 1->2, 2->4, 3->8, 4->6

1: (0, 0, 1) -> 1
2: (1, 0, 1) -> 2
3: (1, 0, 3) -> 6
4: (3, 0, 3) -> 4
5: (3, 1, 3) -> (2, 0, 3) -> 2
6: (4, 1, 9) -> (3, 0, 9) -> 2
7: (3, 0, 3) -> 4
8: (6, 0, 3) -> 2
9: (6, 0, 7) -> 8
10: (6, 0, 7)-> 8
11: (6, 0, 7) -> 8

论坛徽章:
0
5 [报告]
发表于 2006-05-15 21:04 |只看该作者
这个估计跟 yuxh 给出的代码背后的想法是一致的。

我就等着看 O(log N) 了,

论坛徽章:
0
6 [报告]
发表于 2006-05-16 09:14 |只看该作者
解法一:
第一步,对于所有的数(根据题意<25000)先用O(nlog(n))的算法算出来将结果保存到文件里面去。
第二步,以后每次需要的时候,直接从结果文件里面检索。

解法二:

申明数组用于保存每个数对应的结果
读取一个数,
如果这个数小于当前已经算过的数,那么直接返回数组里面对应的结果
如果大于当前已经算过的数,那么从当前已经算过的数一直算到这个数,并且把每个数的结果保存到数组里面去。

[ 本帖最后由 jkit 于 2006-5-16 09:22 编辑 ]

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
7 [报告]
发表于 2006-05-16 17:38 |只看该作者
我得到的O(log(n))的程序:
  1. int fac2[] = {6,2,4,8};
  2. int facn[] = {6,6,2,6,4};
  3. long long cnt[30][4]={{1,1,3,0}}, cnt_len = 1;
  4. int co_list[30], list_len=0;

  5. int get_val(int v, int b, int k)
  6. {
  7.     v = v * facn[b] * fac2[(k*b)%4] % 10;
  8.     return v;
  9. }

  10. void preproc(long long n)
  11. {
  12.     int k, i, j, mul;
  13.     for(k=0; n>0; n/=5, k++) {
  14.         co_list[k] = n%5;
  15.         if(k >= cnt_len) {
  16.             memset(cnt[k], 0, sizeof(long long)*4);
  17.             for(j=0; j<5; j++) {
  18.                 mul = get_val(1, j, k);
  19.                 for(i=0; i<4; i++)
  20.                     cnt[k][((mul*2*(i+1))%10)/2-1] += cnt[k-1][i];
  21.             }
  22.             cnt_len++;
  23.         }
  24.     }
  25.     list_len = k-1;
  26. }

  27. long long calc(long long n)
  28. {
  29.     int i, j, k, mul = 1, v=1;
  30.     long long val = 0, part[4];
  31.     preproc(n);
  32.     memset(part,0,sizeof(part));
  33.     for(i=list_len; i>0; i--) {
  34.         for(j=1; j<=co_list[i]; j++) {
  35.             mul = get_val(v, j-1, i);
  36.             for(k=0; k<4; k++) {
  37.                 part[((mul*2*(k+1))%10)/2-1] += cnt[i-1][k];
  38.             }
  39.         }
  40.         v = get_val(v, j-1, i);
  41.     }
  42.     for(j=0; j<=co_list[0]; j++) {
  43.         mul = get_val(v, j, 0);
  44.         part[mul/2-1]++;
  45.     }
  46.     val += 2*part[0] + 4*part[1] + 6*part[2] + 8*part[3] - 11;
  47.     return val;
  48. }

  49. int main()
  50. {
  51.     long long n;
  52.     while(1){
  53.         printf("input n:");
  54.         scanf("%lld", &n);
  55.         if(n == 0) break;
  56.         printf("result:%lld\n", calc(n));
  57.     }
  58. }
复制代码

提示:
用f(n)表示n!的最右不为0的数字,则可以得到下面的公式:
设n=Ak*5^k+...+A1*5+A0(0<=Ai<=4)为其5进制展开
则f(n)=f(A0)f(A1)...f(Ak)*2^(A1+2A2+...+kAk)%10

评分

参与人数 1可用积分 +3 收起 理由
win_hate + 3 我很赞同

查看全部评分

论坛徽章:
0
8 [报告]
发表于 2006-05-17 10:29 |只看该作者
to yuxh:

设 n = 5*q + r, 则

f(n) = r!*f(q) 2^q    mod 10

q = (A_K...A_1),注意到 2^5 = 2 mod 10,所以上式子也可以写为(r=A_0):

f(n) = (A_0!) * f(q) 2^{A_1+...+ A_k}   mod 10                (R)

而在这个的基础上迭代就可以得到你给出的公式。

直接用公式 R 计算 f(n) 很快,但求 sum  的时候就慢了,因为要不断除5。在一开始就使用 5 进制,可以避免对每个 n 进行拆分。从 n 过渡到 n-1,只需要把 A_0 减去 1 并做进位处理。效率上的改善主要体现在这里,对吗?


我没看懂你的代码,只是从你给出的公式进行分析的,你看看我的理解是否与你的一致?能不能说一下你的代码?你是把每一个 f(n) 都求出来了还是怎么的?

单纯求 f(n) 我有一个类似的公式,但用来求和也比较慢。

论坛徽章:
1
荣誉会员
日期:2011-11-23 16:44:17
9 [报告]
发表于 2006-05-17 11:22 |只看该作者
气死我了
打了半天的字,一发送,报告来路不正。。。

论坛徽章:
0
10 [报告]
发表于 2006-05-17 11:35 |只看该作者
呵呵,先保存一下嘛。

看看这个:

g(n) 表示 n! 分解中不含因子 5 的部分,若 n =(A_k....A_0)_5 则

g(n) = A_0! * g (A_k ... A_1)_5

这样就可以用动态规划把 g(n) 求出来。 _5 表示 5 进制
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP