PAT A1010. Radix PAT和牛客网全A思路以及PAT上测试例10的讨论

Given a pair of positive integers, for example, 6 and 110, can this equation 6 = 110 be true? The answer is "yes", if 6 is a decimal number and 110 is a binary number.

Now for any pair of positive integers N1 and N2, your task is to find the radix of one number while that of the other is given.

Input Specification:

Each input file contains one test case. Each case occupies a line which contains 4 positive integers:
N1 N2 tag radix
Here N1 and N2 each has no more than 10 digits. A digit is less than its radix and is chosen from the set {0-9, a-z} where 0-9 represent the decimal numbers 0-9, and a-z represent the decimal numbers 10-35. The last number "radix" is the radix of N1 if "tag" is 1, or of N2 if "tag" is 2.

Output Specification:

For each test case, print in one line the radix of the other number so that the equation N1 = N2 is true. If the equation is impossible, print "Impossible". If the solution is not unique, output the smallest possible radix.

Sample Input 1:
6 110 1 10
Sample Output 1:
2
Sample Input 2:
1 ab 1 2
Sample Output 2:

Impossible


/*********************侯尼玛*********************/

PAT甲级里确实水题也不少,想着在博客上应该多记录一些比较复杂难搞的题目,所以特意挑了这道似乎是迄今为止甲级里通过率最低的题目,通过率只有0.08。

简单解释一下题目:给出两个数,其中一个为已知数,题目会给出它的Radix;而另一个为未知数,题目不会给出它的Radix。要求输出未知数的一个合适的Radix,使未知数的值与已知数相等。(Radix大概就是进制数的意思,例如十进制数的Radix就是10

这道题乍一看也确实不难,但是偏偏就是有很多测试例迷之过不去。并且在这道题上我第一次遇到了这种情况———在牛客网上全AC的代码在PAT上却错了好几个测试例,这大概说明PAT的测试例还是更加靠谱和全面一点的。反复修改测试以后我发现有这么几个重点:

(1)首先Radix是必须大于全部位上最大的数字的。这是显然的,例如123456z,至少得是36进制的。

(2)其次未知数的Radix是没有上限的,有可能会非常大。因此如果只是简单地循环累加Radix,一遍一遍计算未知数并和已知数比较的的话肯定会超时。至少得用二分法之类的方法来加快速度。

(3)如果使用二分法,未知数的Radix的上限一般选择为已知数的值(因为在特殊情况下,比如abcd 10 1 40这样的测试例,输出的Radix就是二分的上限,abcd的值。所以radix的上限一定要足够大)。这样Radix可能会被选择为一个很大的值。这种情况下,如果未知数的数字本身也是一个比较长的字符串,那么计算未知数的值时很可能会超出long long的存储范围,导致错误。(这点解释了为什么在牛客网全A的代码在PAT却会有很多错误,因为牛客网的测试例中未知数的数字都比较短,例如“jl”之类的,只有两位,自然不会溢出了。但是PAT里的未知数数字都很长,随随便便就溢出了,用二分法时如果用计算出来的未知数的值进行比较,会很不安全。)于是稍作改进。在最开始改进的思路是:在计算未知数的值时,每取一位时就和已知数的值作比较。只要在溢出前一步能够发现未知数的值比已知数大,返回这个结果,就能避免大部分的错误了。

(4)就是这个想法,导致了测试例10的错误。我之前特费解,测试例10的内容到底是啥,好像有很多人错,甚至还有人说测试例10有问题,PAT平台上的答案有错误。看到有人贴出的全A的代码中,只要添上一段if(str1==str2) cout<<Radix;这样的代码,就能通过测试例10,我更加不解了。很显然这段代码是错误的。举个例子,如果一个测试例的内容是“1 1 1 10”,显然正确答案是2。但是如果添上上述的那段代码,这个测试例的输出会变成10,是错误的。那么为什么添上这段代码就能通过测试例10呢?经过反复的测试,我发现测试例10的内容是:“zzzzzzzzzz zzzzzzzzzz 1 40”(测试的过程挺不容易,先测试数字的位数,再测首字符和Radix,最后得到这个结论,平白多刷了三十来次提交2333)拿这个测试例来测试我的代码,会发现竟然输出了“impossible”,而不是40!到底怎么回事呢?添加一些额外的输出,终于明白了原因:如图,

输出了在二分过程中radix的取值范围,可以发现当Radix的二分中位数为35897435932时,对未知数和已知数的比较发生了错误。经过调试,发现比较函数中,在Radix等于35897435932的情况下,未知数的值在第二位就溢出成为了负数。更加关键的是,剩下整整8位(zzzzzzzzzz共10位的z)的计算过程中未知数的值都刚好溢出为负数,再没有一次比已知数的值大,以致于产生了这样的bug。

发现了原因就非常容易解决了。一开始想说只要计算的未知数值为负数,就说明一定溢出了。不过这个方法还是比较蠢,看到了别人的代码中有一个更好的办法:如果未知数当前的值大于0x7fffffffffffffff/Radix(也就是longlong的最大正数值除以当前的Radix),那么下一位就肯定会溢出。只要在比较函数中添加了这个判断,就能保证肯定不会溢出了。

下面附上我的代码:

#include <iostream>
#include <string>
using namespace std;

int DigitNum(char a)
{
    if(a<='9' && a>='0')
        return a-'0';
    else if(a>='a' && a<='z')
        return 10+a-'a';
    else return 0;
}

long long TurnTo10(string str,long long Radix)
{
    long long Int10 =0;
    for(size_t i = 0; i<str.size(); i++)
    {
        Int10=Int10*Radix+DigitNum(str[i]);
    }

    return Int10;
}

int CompareNum(string str,long long Radix,long long Num1)
{
    long long Int10 =0;
    for(size_t i = 0; i<str.size(); i++)
    {
        Int10=Int10*Radix+DigitNum(str[i]);
        if(Int10>Num1) return 2;
        if(Int10>0x7FFFFFFFFFFFFFFF/Radix) return 2;
    }
    if(Int10<Num1) return 1;
    else if(Int10==Num1) return 0;

}

int main()
{
    string str1,str2;
    long long WhichNum,Radix;
    while(cin>>str1>>str2>>WhichNum>>Radix)
    {
        long long Num1;
        string TempStr;
        if(WhichNum==1)
        {
            Num1=TurnTo10(str1,Radix);
            TempStr=str2;
        }
        else
        {
            Num1=TurnTo10(str2,Radix);
            TempStr=str1;
        }
        long long RadixOfTemp=0;
        for(size_t i=0;i<TempStr.size();i++)
        {
            if(DigitNum(TempStr[i])+1>RadixOfTemp)
                RadixOfTemp=DigitNum(TempStr[i])+1;
        }

        /****偷懒的方法,反倒可以在忽视第四点的基础上全通测试例****
        long long Num2=TurnTo10(TempStr,RadixOfTemp);
        long long Step = 1000000000;
        while(Step>0)
        {

            while(CompareNum(TempStr,RadixOfTemp,Num1)==1)
            {
                RadixOfTemp+=Step;
            }
            if(CompareNum(TempStr,RadixOfTemp,Num1)==0)
            {
                cout<<RadixOfTemp;
                break;
            }
            else if(CompareNum(TempStr,RadixOfTemp,Num1)==2)
            {
                RadixOfTemp-=Step;
            }
            Step/=10;
        }
        ****/

        /*****二分法*****/
        long long High = Num1+1;
        long long Low = RadixOfTemp;
        if(CompareNum(TempStr,High,Num1)==1)
        {
            cout<<"Impossible";
            continue;
        }
        long long Mid;
        while(Low<=High)
        {
            Mid = (High+Low)/2;
            int Result = CompareNum(TempStr,Mid,Num1);
            switch (Result)
            {
            case 2:
                {
                    High = Mid -1;
                    //cout<<Low<<" < "<<"Radix"<<" < "<<Mid<<endl;
                    break;
                }
            case 1:
                {
                    Low = Mid +1;
                    //cout<<Mid<<" < "<<"Radix"<<" < "<<High<<endl;
                    break;
                }
            case 0:
                {
                    cout<<Mid;
                    //cout<<'='<<Mid<<endl;
                    break;
                }
            }
            if(Result == 0)
                break;
        }

        /*****/
        if(Low>High)
        //if(CompareNum(TempStr,RadixOfTemp,Num1)!=0)
            cout<<"Impossible";
    }
    return 0;
}



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值