PAT甲级 1010 Radix 详细题解

1010 Radix

用时53分钟(这个时间记的可能还不是满的,中间有一些思考时间没计进来……别扶我起来了,我不行了

题意是给出两个数和其中一个数的进制,问另外一个数在什么进制下可以与另外一个数相等,如果任何进制下都不能与另外一个数相等,就输出impossible,如果有多个进制满足条件,输出最小的那一个

这个题我做的着实是 有点难顶

最开始我没仔细想这个题的内部逻辑,我是这么暴力做的:

①计算出已经确定进制的数的真值(使用BigInteger存放)

②计算另外一个数的最低进制(例:如果一个数中出现了字符7,那么它最低是八进制;如果一个数中出现了字符d,那么它最低是14进制),将最低进制记为minRadix

③从minRadix到10000枚举另外一个数的进制,按枚举的这个进制计算这个数的真值(用BigInteger存放),与第一步中计算出来的真值进行比对,如果一样,就输出(因为我是从小到大枚举的,所以这样一定能保证是最小的);如果已经找到10000了还没有找到一个一样的,就输出Impossible

注:这里为什么上限设的是10000呢?因为上限设100000就超时了。。。

这样写的代码如下,结果是测试用例7过不掉

import java.io.BufferedInputStream;
import java.math.BigInteger;
import java.util.Scanner;
​
import static java.lang.Math.max;
​
public class Main {
    static BigInteger trueValue(String n, String radix) {
        //按radix进制计算字符串n的真值是多少(用BigInteger进行存储)
        char[] array = n.toCharArray();
        BigInteger result = new BigInteger("0");
        BigInteger rradix = new BigInteger(radix);
        BigInteger coeff = new BigInteger("1");
        for(int i = array.length - 1; i >= 0; i--) {
            int tt = 0;
            if(array[i] >= '0' && array[i] <= '9') {
                tt = array[i] - '0';
            } else if(array[i] >= 'a' && array[i] <= 'z') {
                tt = array[i] - 'a' + 10;
            }
            String t = String.valueOf(tt);            //当前这一位
            BigInteger b = new BigInteger(t).multiply(coeff);
            coeff = coeff.multiply(rradix);
            result = result.add(b);
        }
        return result;
    }
​
    static int findMinRadix(String s) {
        //给一个字符串,求它的最低进制-1(这里是当时写的时候没想清楚,所以和最低进制差了1)
        char[] array = s.toCharArray();
        int m = 0;
        for(int i = 0; i < array.length; i++) {
            if(array[i] >= '0' && array[i] <= '9') {
                m = max(m, array[i]-'0');
            } else if(array[i] >= 'a' && array[i] <= 'z') {
                m = max(m, array[i] - 'a' +10);
            }
        }
        return m;
    }
​
​
    public static void main(String[] args) {
        Scanner scanner = new Scanner(new BufferedInputStream(System.in));
        String s = scanner.nextLine();
        String[] array = s.split(" ");
        String n1 = array[0];
        String n2 = array[1];
        String tag = array[2];
        String radix = array[3];
        if(!tag.equals("1")) {
            //交换
            String t = n1;
            n1 = n2;
            n2 = t;
        }
        BigInteger value1 = trueValue(n1, radix);
        boolean flag = false;
        int minRadix = Math.max(findMinRadix(n2),1);
        for(int i = minRadix+1; i < 10000; i++) {
            //a digit is less than its radix(不能等于),所以需要从minRadix+1起步
            BigInteger value2 = trueValue(n2, String.valueOf(i));
            if(value1.compareTo(value2) == 0) {
                System.out.println(i);
                flag = true;
                break;
            }
        }
        if(!flag) {
            System.out.println("Impossible");
        }
    }
}

 

虽然逻辑简单,但是AC不了,恼火,测试用例7过不了的用时还非常长,合理推测是我的程序没有找到它真正对应的进制,根据我上一版程序的逻辑,问题只能出现在10000这个常数上,他可能是十万进制的。。。想了想可以很容易地给出这样的一组例子:

100000 10 1 10

比如说上述样例就是需要一个十万进制的样例。。。。

已知数据是可以上到十位的,并且每位最多上到z,所以这是不可能枚举到的,于是想到了二分,但是想用二分需要先证明二分的合理性:

我们可以简单理解一下这件事情(理解这部分我用引用标记框起来了,这样比较好看一点):

在以下内容中,我们假设value1是已知进制的数的真值,value2是未知进制数在mid进制下的真值

首先我们需要确定一个前提(这是一个最初步的结论,后面会细化这个结论):那就是随着进制的增大,这个未知进制数的值一定是不会减的(有可能不增,但一定不会减),原因如下:

我们不妨假设这个未知进制的串为c1 c2 c3 …… cn,其中cn是最低位,c1是最高位,当前进制是r进制,那么这个数的真值value2应该等于c1*[r^(n-1)] + c2*[r^(n-2)] + c3*[r^(n-3)] + …… + cn*[(r^0)],在这个式子中,c1 c2 …… cn都是非负数,所以,这个式子整体取值一定会随着r的值增加而不递减(绝大多数情况下都应该是递增的,但也不尽然,我们可以举出c1=c2=c3=……=cn=0 的例子来反驳一定递增的结论,这种情况下不管取多少进制,结果都应该是0)

有了上面这个前提,接下来我们就可以进行分析了:

如果value2 < value1,那么一定是进制小了

类似的,如果value2 > value1,那么一定是进制大了

如果value2 == value1,说明我们找到了一个合适的进制,但是这个进制究竟是不是最小的呢?

不一定!

考虑同一个串(仍然假设为c1 c2 c3 …… cn)的两个进制r1和r2(我们假设r1>r2),在这两个进制下的真值应该分别为:

c1 * [r1 ^ (n-1)] + c2 * [r1 ^ (n-2)] + …… + cn * [r1 ^ 0]

c1 * [r2 ^ (n-1)] + c2 * [r2 ^ (n-2)] + …… + cn * [r2 ^ 0]

两者求差,得:

c1 * {[r1 ^ (n-1)] - [r2 ^ (n-1)]} + c2 * {[r1 ^ (n-2)] - [r2 ^ (n-2)]} + …… + cn * {r1 ^ 0 - r2 ^ 0}

其中最后一项的值固定为0

我们可以看到,除了cn以外的所有项,只要它不是0,并且r1和r2不相等,那么这项的乘积一定不是0,并且在我们假定的条件下(r1 > r2),所有的这些项的符号一定都为正;而cn这一项的值固定为0,它不影响符号

什么意思呢?就是说在r1 > r2的前提下,只要你不只有cn这一项,就一定会得到r1进制下的真值大于r2进制下的真值的结论。这句话也可以反过来说,那就是:什么时候value1 == value2,但是这个进制可能不是最小的呢?只有当你的串仅有cn的时候(相当于是串只有一位)!

总结来说:

  • 当我们未知进制的串仅有1位的时候,不管这个串是多少进制的,它的真值都是一样的,所以我们可以直接取它的最小的合理进制(即上一版程序第②步中计算出的最低进制minRadix),如果在minRadix进制下它的真值和另外一个数的真值相同,那么minRadix就是答案;否则直接输出impossible

  • 在其他情况下(未知进制的串不止1位),我们有未知进制的串的真值随进制radix的值增长而严格递增的结论,因此可以用二分查找的方法来寻找答案(注:初始情况下left应该是minRadix,right应该是已知进制数的真值value1,这里就不赘述做证明了)

到此,终于齐全了,AC代码如下:

import java.io.BufferedInputStream;
import java.math.BigInteger;
import java.util.Scanner;
​
import static java.lang.Math.max;
​
public class Main {
    static BigInteger trueValue(String n, BigInteger radix) {
        //这里我把参数换成了BigInteger类型
        char[] array = n.toCharArray();
        BigInteger result = new BigInteger("0");
        BigInteger coeff = new BigInteger("1");
        for(int i = array.length - 1; i >= 0; i--) {
            int tt = 0;
            if(array[i] >= '0' && array[i] <= '9') {
                tt = array[i] - '0';
            } else if(array[i] >= 'a' && array[i] <= 'z') {
                tt = array[i] - 'a' + 10;
            }
            String t = String.valueOf(tt);            //当前这一位
            BigInteger b = new BigInteger(t).multiply(coeff);
            coeff = coeff.multiply(radix);
            result = result.add(b);
        }
        return result;
    }
​
    static int findMinRadix(String s) {
        char[] array = s.toCharArray();
        int m = 0;
        for(int i = 0; i < array.length; i++) {
            if(array[i] >= '0' && array[i] <= '9') {
                m = max(m, array[i]-'0');
            } else if(array[i] >= 'a' && array[i] <= 'z') {
                m = max(m, array[i] - 'a' +10);
            }
        }
        return m;
    }
​
​
    public static void main(String[] args) {
        Scanner scanner = new Scanner(new BufferedInputStream(System.in));
        String s = scanner.nextLine();
        String[] array = s.split(" ");
        String n1 = array[0];
        String n2 = array[1];
        String tag = array[2];
        String radix = array[3];
        if(!tag.equals("1")) {
            //交换
            String t = n1;
            n1 = n2;
            n2 = t;
        }
        BigInteger value1 = trueValue(n1, new BigInteger(radix));
        int minRadix = Math.max(findMinRadix(n2),1);
        BigInteger left = new BigInteger(String.valueOf(minRadix));
        if(n2.length() == 1) {
            //如果未知进制的串长度为1,需要特殊处理
            if(trueValue(n2, left).compareTo(value1) == 0) {
                //如果相等,直接输出minRadix(这里加1是因为我的函数返回值少了1)
                System.out.println(minRadix+1);
                return;
            } else {
                //如果不等,直接Impossible
                System.out.println("Impossible");
                return;
            }
        }
        //value1就是maxRadix的初始值
        BigInteger two = new BigInteger("2");
        BigInteger right = new BigInteger(value1.toString());
        //开始二分查找
        while(left.compareTo(right) <= 0) {
            BigInteger rradix = left.add(right).divide(two);
            BigInteger value2 = trueValue(n2, rradix);
            if(value1.compareTo(value2) == 0) {
                System.out.println(rradix.toString());
                return;
            } else if(value1.compareTo(value2) > 0) {
                //value1 > value2,说明radix小了
                left = rradix.add(BigInteger.ONE);
            } else {
                //value2大了,说明radix大了
                right = rradix.subtract(BigInteger.ONE);
            }
        }
        BigInteger value2 = trueValue(n2, left);
        if(value1.compareTo(value2) == 0) {
            System.out.println(left.toString());
            return;
        }
        System.out.println("Impossible");
    }
}
​

 

PAT还是挺有意思的~

扶我起来,我还能做!

 

posted @ 2020-04-01 23:18  RiddleLi  阅读(474)  评论(0编辑  收藏  举报