【编程马拉松】【011-鸽兔同校】

【编程马拉松算法目录>>>】


【011-鸽兔同校】【工程下载>>>】


1 题目描述


  浙江大学校园里绿树成荫,环境非常舒适,因此也引来一批动物朋友来此居住。
  童心未泯的NowCoder就经常带些碎面包什么的去广场喂鸽子和兔子,并和它们玩耍。 一点也没有大学生的样子,还是一副老不正经的样子,呵呵。
  随着鸽子和兔子数目的增多,NowCoder带的那点食物已经不够它们瓜分了。为了能让自己的好朋友吃的饱饱的, NowCoder决定统计一下有多少只鸽子和有多少只兔子,以便带来足够的食物。一、二、三、四、五…他开始数了。
  现在,他已经知道有这些鸽子和兔子一共有n个头和m只脚。请你帮他写个程序计算一下一共有多少只鸽子和兔子。

1.1 输入描述:


  输入包括多组数据。
  每行包括2个正整数n和m,n和m可能会很大,超过2^64,但位数不超过100位。

1.2 输出描述:


  每组数据的输出都只有一行,分别是鸽子的数量和兔子数量。
  如果输入的测试数据不能求得结果,那肯定是NowCoder这个马大哈数错了,就输出“Error”提示他。

1.3 输入例子:


35 94
1 3

1.4 输出例子:


23 12
Error

2 解题思路


  假设有x只鸽子,y只兔子,它们的头和脚的数目分别是m和n。则有:

{x+y=m2x+4y=n{x=4mn2y=n2m2xNyN 

  x和y都是自然数⇒n为偶数, x=4mn24mn 为偶数(一定成立,因为n为偶数), y=n2m2n2m 为偶数(一定成立,因为n为偶数)。同时因为m和n非常大,要使用大整数进行操作。

3 算法实现

import java.util.Scanner;


/**
 * Author: 王俊超
 * Time: 2016-05-09 13:55
 * CSDN: http://blog.csdn.net/derrantcm
 * Github: https://github.com/Wang-Jun-Chao
 * Declaration: All Rights Reserved !!!
 */
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
//        Scanner scanner = new Scanner(Main.class.getClassLoader().getResourceAsStream("data.txt"));
        while (scanner.hasNext()) {
            String m = scanner.next();
            String n = scanner.next();
            String[] r = pigeonAndRabbit(m, n);
            if (r == null) {
                System.out.println("Error");
            } else {
                System.out.println(r[0] + " " + r[1]);
            }
        }

        scanner.close();
    }

    /**
     * 计算鸽子和兔子的数目
     *
     * @param ms 鸽子和兔子的头数
     * @param ns 鸽子和兔子的脚数
     * @return 长度为2的数符串,分别表示鸽子的数量和兔子数量,如果无解就返回null
     */
    private static String[] pigeonAndRabbit(String ms, String ns) {

        int lastN = ns.charAt(ns.length() - 1) - '0';
        // ns为偶数
        if (lastN % 2 != 0) {
            return null;
        }

        int[] m = getNumber(ms);
        int[] n = getNumber(ns);

        // 鸽子数
        // 4 * m
        int[] x = multiply(m, new int[]{4});

        // 兔子数
        // 2 * m
        int[] y = multiply(m, new int[]{2});

        // 4m >= n && n >= 2m
        if (compare(x, n) >= 0 && compare(n, y) >= 0) {

            // 4m - n
            x = minus(x, n);
            // (4m - n) / 2
            x = divide2(x);

            // n - 2m
            y = minus(n, y);
            // (n - 2m) / 2
            y = divide2(y);

            return new String[]{toNumber(x), toNumber(y)};

        } else {
            return null;
        }
    }

    /**
     * 将整数字符串表示成整数数组
     *
     * @param n 整数字符串
     * @return 整数数组 下标从小到大表示数位的从低到高
     */
    private static int[] getNumber(String n) {
        int[] r = new int[n.length()];
        for (int i = 0; i < r.length; i++) {
            r[i] = n.charAt(n.length() - i - 1) - '0';
        }

        return r;
    }

    /**
     * 两个数相乘
     *
     * @param m 乘数
     * @param n 乘数
     * @return 结果
     */
    private static int[] multiply(int[] m, int[] n) {

        // 结果最多的位数
        int[] r = new int[m.length + n.length];
        // 来自低位的进位
        int c;

        int t;
        int k;

        for (int i = 0; i < n.length; i++) {
            // 计算n[i]*m

            if (n[i] == 0) {
                continue;
            }

            c = 0;
            for (int j = 0; j < m.length; j++) {
                t = n[i] * m[j] + r[i + j] + c;
                r[i + j] = t % 10;
                c = t / 10;

            }

            // 如果还有进位要继续处理
            k = i + m.length;
            while (c != 0) {
                t = c + r[k];
                r[k] = t % 10;
                c = t / 10;
                k++;
            }
        }

        return r;
    }

    /**
     * 两个整数相加
     *
     * @param m 整数
     * @param n 整数
     * @return 结果
     */
    private static int[] add(int[] m, int[] n) {

        // 保证n不小于m
        if (m.length > n.length) {
            int[] t = m;
            m = n;
            n = t;
        }

        // 结果的最大长度
        int[] r = new int[n.length + 1];
        // 来自低位的进位
        int c = 0;

        for (int i = 0; i < m.length; i++) {
            r[i] = m[i] + n[i] + c;
            c = r[i] / 10;
            r[i] %= 10;
        }

        // 计算余下的部分
        for (int i = m.length; i < n.length; i++) {
            r[i] = n[i] + c;
            c = r[i] / 10;
            r[i] %= 10;
        }

        // 最后还有进位
        if (c != 0) {
            r[r.length - 1] = c;
            return r;
        }
        // 没有进位
        else {
            int[] ret = new int[r.length - 1];
            System.arraycopy(r, 0, ret, 0, ret.length);
            return ret;
        }
    }

    /**
     * 比较两个整数是否相等,下标由小到大表示由低位到高位,忽略最高有效位上的前导0
     *
     * @param m 整数
     * @param n 整数
     * @return m > n返回1,m = n返回0,m < n返回-1
     */
    private static int compare(int[] m, int[] n) {

        if (m == null && n == null) {
            return 0;
        }
        // null最小
        if (m == null) {
            return -1;
        }

        if (n == null) {
            return 1;
        }

        int lastM = m.length - 1;
        int lastN = n.length - 1;

        // 找m的最高有效位的位置,至少有一位
        while (lastM >= 1 && m[lastM] == 0) {
            lastM--;
        }
        // 找n的最高有效位的位置,至少有一位
        while (lastN >= 1 && n[lastN] == 0) {
            lastN--;
        }

        // m的数位比n多,说明m比n大
        if (lastM > lastN) {
            return 1;
        }
        // m的数位比n少,说明m比n小
        else if (lastM < lastN) {
            return -1;
        } else {
            // 位数一样,比较每一个数位上的值,从高位到低位进行比较
            for (int i = lastM; i >= 0; i--) {
                if (m[i] > n[i]) {
                    return 1;
                } else if (m[i] < n[i]) {
                    return -1;
                }
            }

            return 0;
        }
    }

    /**
     * 做减法n-m,保证n大于等于m
     *
     * @param n 整数
     * @param m 整数
     * @return 结果
     */
    private static int[] minus(int[] n, int[] m) {

        n = format(n);
        m = format(m);

        int[] r = new int[n.length];

        // 当前位被借位
        int c = 0;
        int t;
        for (int i = 0; i < m.length; i++) {
            t = n[i] - c - m[i];
            // 当前位够减
            if (t >= 0) {
                r[i] = t;
                // 没有进行借位
                c = 0;
            }
            // 不够减
            else {
                r[i] = t + 10;
                // 进行借位
                c = 1;
            }
        }

        // 还有借位
        for (int i = m.length; c != 0 && i < n.length; i++) {
            t = n[i] - c;
            // 当前位够减
            if (t >= 0) {
                r[i] = t;
                // 没有进行借位
                c = 0;
            }
            // 不够减
            else {
                r[i] = t + 10;
                // 进行借位
                c = 1;
            }
        }

        return format(r);
    }

    /**
     * 将整数进行格式化,去掉高位的前导0
     *
     * @param r 整数
     * @return 结果
     */
    private static int[] format(int[] r) {
        int t = r.length - 1;
        // 找最高有效位
        while (t > 0 && r[t] == 0) {
            t--;
        }

        int[] nr = new int[t + 1];
        System.arraycopy(r, 0, nr, 0, nr.length);
        return nr;

    }

    /**
     * 将数n除以2
     *
     * @param n 整数
     * @return 结果
     */
    private static int[] divide2(int[] n) {
        // 结果
        int[] r = new int[n.length];
        // 上一位除以2后的余数
        int c = 0;
        int t;

        for (int i = n.length - 1; i >= 0; i--) {
            t = c * 10 + n[i];
            r[i] = t / 2;
            c = t % 2;
        }

        return format(r);
    }

    /**
     * 将数组表示的整数转换成字符串
     *
     * @param r 整数
     * @return 字符串表示的整数
     */
    private static String toNumber(int[] r) {
        if (r == null) {
            return null;
        }

        StringBuilder b = new StringBuilder(r.length);

        for (int i = r.length - 1; i >= 0; i--) {
            b.append(r[i]);
        }

        return b.toString();
    }
}

4 测试结果


这里写图片描述

5 其它信息


因为markddow不好编辑,因此将文档的图片上传以供阅读。Pdf和Word文档可以在Github上进行【下载>>>】

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值