poj 1150 The Last Non-zero Digit

http://poj.org/problem?id=1150

  一道题意相当简单的数论题,求P(n,m)的最后一个非零位的数字,其中0≤m≤n≤2*10^7。直接暴力大数运算是会超时的,这里要用到一些数论的知识。

  引用一下wyh师兄的解释:http://www.cppblog.com/wyh123/archive/2011/08/06/152621.html

  排列公式:P(n,m)=n!/(n-m)!

  将一个数2和5的因子都分离出来,最后这个数就会变成个位是1,3,7,9的数。其中,个位是1的数不会影响到最后一位非零数字。于是,我们就要统计那个区间内,将2和5两种因子的数去掉以后的那些数的最后一位是3,7,9的个数。统计的时候有个技巧,需要观察到,每十个数以内末数字是3,7,9在没有除去2和5两种因子前都只会出现一次。然后我打了个表出来观察,发现除去2和5的操作,实际上就是从原数列中抽出2和5的倍数来出以2和5。除了一次2或5以后,抽出来的序列又变成了从1~n/2或n/5的连续整数了。继续递归下去,从而统计出末数字是3,7,9的数在该区间中的个数。

  不过分别处理除2和除5的时候,10的倍数的数会被除去两次,这时就要用容斥定理来补回10的倍数的数了,所以就得到下面的程序。

  如果没有预处理小数据的情况,每次计算最多将递归计算10^6以上,但是如果把小数据先处理了,那么每次查询的时候就可以只用不到10^3就可以查询到一组数据了。

代码如下:

View Code
 1 #include <cstdio>
 2 
 3 const int maxn = 200000;
 4 int tmp[maxn][10];
 5 
 6 void pre() {
 7     for (int i = 1; i < maxn; i++) {
 8         int t = i;
 9 
10         while (t % 2 == 0) t /= 2;
11         while (t % 5 == 0) t /= 5;
12         for (int j = 0; j < 10; j++) {
13             tmp[i][j] = tmp[i - 1][j];
14         }
15         tmp[i][t % 10]++;
16     }
17 }
18 
19 int cal(int n, int k) {
20 //    printf("cal!!! %d %d\n", n, k);
21     if (n < maxn) return tmp[n][k];
22     else return n / 10 + cal(n / 2, k) + cal(n / 5, k) + (n % 10 >= k) - cal(n / 10, k);
23 }
24 
25 int deal(int n) {
26     int ret1 = 0, ret2 = 0;
27     int t = n;
28 
29     while (t) {
30         ret1 += t >> 1;
31         t >>= 1;
32     }
33     while (n) {
34         ret2 += n / 5;
35         n /= 5;
36     }
37 //    printf("ret1 %d   ret2 %d\n", ret1, ret2);
38 //    puts("!!!");
39 
40     return ret1 - ret2;
41 }
42 
43 int main() {
44     int cnt3, cnt7, cnt9, cnt2;
45     int n, m;
46 
47     pre();
48     while (~scanf("%d%d", &n, &m)) {
49         if (m == 0){
50             puts("1");
51             continue;
52         }
53 
54 //        puts("~~~");
55         m = n - m;
56 //        printf("n %d  m %d\n", n, m);
57 
58         cnt3 = (cal(n, 3) - cal(m, 3)) & 3;
59 //        puts("~~~1");
60         cnt7 = (cal(n, 7) - cal(m, 7)) & 3;
61 //        puts("~~~2");
62         cnt9 = (cal(n, 9) - cal(m, 9)) & 1;
63 //        puts("~~~~~~~");
64         cnt2 = deal(n) - deal(m);
65         if (cnt2) cnt2 = (cnt2 - 1) % 4 + 1;
66 //        printf("%d %d %d %d\n", cnt3, cnt7, cnt9, cnt2);
67 
68         int ans = 1;
69 
70         while (cnt3--) {
71             ans *= 3;
72         }
73         while (cnt7--) {
74             ans *= 7;
75         }
76         while (cnt9--) {
77             ans *= 9;
78         }
79         if (cnt2 > 0) {
80             while (cnt2--) {
81                 ans <<= 1;
82             }
83         } else {
84             while (cnt2++) {
85                 ans *= 5;
86             }
87         }
88         ans %= 10;
89 
90         printf("%d\n", ans);
91     }
92 
93     return 0;
94 }

 

——written by Lyon

转载于:https://www.cnblogs.com/LyonLys/archive/2012/09/28/poj_1150_Lyon.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值