挑战程序竞赛系列(38):4.1模运算的世界(1)

挑战程序竞赛系列(38):4.1模运算的世界(1)

详细代码可以fork下Github上leetcode项目,不定期更新。

练习题如下:

POJ 1150: The Last Non-zero Digit

总算刷到了登峰造极,看了一些关于模运算的相关知识,但还是云里雾里,索性就直接开题,该系列每题都开篇博文讲解吧,知识点比较多。

此题推荐博文:http://www.hankcs.com/program/algorithm/poj-1150-the-last-non-zero-digit.html

更详细的解法:http://duanple.blog.163.com/blog/static/7097176720081016113033592/

数论入门推荐书籍:《初等数论及其应用》.

思路:
进行质因数分解,分解2和5,因为只有2和5的乘积才能构成10。在一个序列中:

1 2 3 4 5 6 7 8 9 10

因数2的个数永远大于因数5的个数

所以说,我们只需要求出多出来的2的个数即可,这些将会影响非零位数的最终结果。

2的质因数个数如何求解:

1 2 3 4 5 6 7 8 9 10

可以分解为:
1 3 5 7 9 | 2 4 6 8 10
前半部分个数:0
后半部分个数:5 + f{1,2,3,4,5}

没错,除了个2之后,能够得到5个质因数
该问题就变成了子问题{1,2,3,4,5}的质因数个数

递归解决,复杂度O(logn)

代码如下:

    public int total2(int n){
        if (n == 0) return 0;
        return n / 2 + total2(n / 2);
    }

质因数5的个数如何?和2一个道理,所以有:

    public int total5(int n){
        if (n == 0) return 0;
        return n / 5 + total5(n / 5);
    }

所以综合下(递归版本):

    public int fact_prime(int n, int x){
        if (n == 0) return 0;
        return n / x + fact_prime(n / x, x);
    }

迭代版本:

    public int fact_prime_iter(int n, int x){
        int res = 0;
        while (n > 0){
            res += n / x;
            n = n / x;
        }
        return res;
    }

接着,把所有的2和5的质因数全部排除后,剩余了什么?

1 2 3 4 5 6 7 8 9 10
1 1 3 1 1 3 7 1 9 1

只剩下{1,3,7,9}了,那么为了能够计算非零位数,统计这些1379的个数就完事了。

统计蛮取巧,分成奇数列和偶数列

1 3 5 7 9 | 2 4 6 8 10
前半部分的个数分别为:1:2 3:1 7:1 9:1
后半部分的个数分别为:子问题{1,2,3,4,5}

子问题可以不去考虑了,前半部分怎么统计呢?
观察发现它包含有5的奇数倍项,但这奇数倍均除以5得到1,3,5,7....
所以变成了前部部分的子问题了...

所以问题的关键就变成了求解1379各自的个数了

其实都不需要求,直接看出来了
当n = 101:1, 3:1, 7:1, 9:1
当n = 131:2 3:2 7:1 9:1
因为13中有1113个位数分别为13

所以给定n
我们有 g(n, x) = n / 10 + (n % 10 >= x) + g(n / 5, x);

n/10: 每10个数都有一个
n%19: n的个位数可能产生一个

好了,代码就出来了:

    public int odd_end_with(int n, int x){
        if (n == 0) return 0;
        int res = n / 10;
        res += (n % 10 >= x ? 1 : 0);
        return res + odd_end_with(n / 5, x);
    }

    public int end_with(int n, int x){
        if (n == 0) return 0;
        return end_with(n / 2, x) + odd_end_with(n, x);
    }

最后如何计算 N!(NM)!

很简单,因为N-M完全包含N,所以直接把统计结果减掉即可,最终代码如下:


import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main{

    String INPUT = "./data/judge/201708/P1150.txt";

    public static void main(String[] args) throws IOException {
        new Main().run();
    }

    int[][] table = {
            {6, 2, 4, 8},
            {1, 3, 9, 7}, // 3  
            {1, 7, 9, 3}, // 7
            {1, 9, 1, 9}, // 9
    };

    void solve() {
        while (more()){
            int N = ni();
            int M = ni();

            // 分解质因数 2 和 5 并且分别统计 2 和 5 的个数
            int two    = fact_prime(N, 2) - fact_prime_iter(N - M, 2);
            int five   = fact_prime(N, 5) - fact_prime_iter(N - M, 5);
            int remain = two - five;

            // 统计 1 3 7 9 的个数
            int three  = end_with(N, 3) - end_with(N - M, 3);
            int seven  = end_with(N, 7) - end_with(N - M, 7);
            int nine   = end_with(N, 9) - end_with(N - M, 9);

            int ans = 1;
            ans *= remain == 0 ? 1 : table[0][remain % 4];
            ans *= three  == 0 ? 1 : table[1][three  % 4];
            ans *= seven  == 0 ? 1 : table[2][seven  % 4];
            ans *= nine   == 0 ? 1 : table[3][nine   % 4];
            ans %= 10;

            out.println(ans);
        }
    }

    public int fact_prime(int n, int x){
        if (n == 0) return 0;
        return n / x + fact_prime(n / x, x);
    }

    public int fact_prime_iter(int n, int x){
        int res = 0;
        while (n > 0){
            res += n / x;
            n = n / x;
        }
        return res;
    }

    public int odd_end_with(int n, int x){
        if (n == 0) return 0;
        int res = n / 10;
        res += (n % 10 >= x ? 1 : 0);
        return res + odd_end_with(n / 5, x);
    }

    public int end_with(int n, int x){
        if (n == 0) return 0;
        return end_with(n / 2, x) + odd_end_with(n, x);
    }

    FastScanner in;
    PrintWriter out;

    void run() throws IOException {
        boolean oj;
        try {
            oj = ! System.getProperty("user.dir").equals("F:\\java_workspace\\leetcode");
        } catch (Exception e) {
            oj = System.getProperty("ONLINE_JUDGE") != null;
        }

        InputStream is = oj ? System.in : new FileInputStream(new File(INPUT));
        in = new FastScanner(is);
        out = new PrintWriter(System.out);
        long s = System.currentTimeMillis();
        solve();
        out.flush();
        if (!oj){
            System.out.println("[" + (System.currentTimeMillis() - s) + "ms]");
        }
    }

    public boolean more(){
        return in.hasNext();
    }

    public int ni(){
        return in.nextInt();
    }

    public long nl(){
        return in.nextLong();
    }

    public double nd(){
        return in.nextDouble();
    }

    public String ns(){
        return in.nextString();
    }

    public char nc(){
        return in.nextChar();
    }

    class FastScanner {
        BufferedReader br;
        StringTokenizer st;
        boolean hasNext;

        public FastScanner(InputStream is) throws IOException {
            br = new BufferedReader(new InputStreamReader(is));
            hasNext = true;
        }

        public String nextToken() {
            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (Exception e) {
                    hasNext = false;
                    return "##";
                }
            }
            return st.nextToken();
        }

        String next = null;
        public boolean hasNext(){
            next = nextToken();
            return hasNext;
        }

        public int nextInt() {
            if (next == null){
                hasNext();
            }
            String more = next;
            next = null;
            return Integer.parseInt(more);
        }

        public long nextLong() {
            if (next == null){
                hasNext();
            }
            String more = next;
            next = null;
            return Long.parseLong(more);
        }

        public double nextDouble() {
            if (next == null){
                hasNext();
            }
            String more = next;
            next = null;
            return Double.parseDouble(more);
        }

        public String nextString(){
            if (next == null){
                hasNext();
            }
            String more = next;
            next = null;
            return more;
        }

        public char nextChar(){
            if (next == null){
                hasNext();
            }
            String more = next;
            next = null;
            return more.charAt(0);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值