每日一道算法题 Day9 of PAT 1010 Radix (25分) 详解!!! 带你避坑!!!!

1010 Radix (25分)


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 N​1​​ and N​2​​ , 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


此题思路简单,坑多,慎之!!!

题意:

任意给你两个十位之内的正数,然后通过tag指定其中一个数,再通过radix指定该数的进制,问是否能找到一个进制使得另外一个数与该数的十进制值相等,若有,则输出进制数,若无,则输出“Impossible”

Attention:


  • 坑一:基数范围

    在这道题中,并不是题目中只给了36个基数表达就说明基数最大为36,我一开始是这么想的,结果很多测试用例跑不通

  • 坑二:查找速度

    既然基数范围不只局限于2 ~ 36,那么如果再用暴力遍历的方法就会导致超时

    最一开始我在跑的时候,暴力遍历2 ~ 100000,结果总共20个测试用例,19个都能跑通,但还有一个钉子户跑不通

    当我把基数上界改为1000000之后,很多测试用例就超时了,之前没跑通的那个测试用例还是跑不通,于是不得已,将遍历改为二分,这个时候,我终于开始仔细考虑基数的上下界了—

  • 坑三:变量类型

    既然这道题的基数可以很大,那么int类型势必是不太够的,本来考虑到既然pow()函数返回值类型是double,那我也用double不就行了(毕竟double超级大啊!!!!),但是用double在暴力遍历法中用是可以的,但是在使用二分法的时候明显会出现小数部分,这就会给最后的判断带来麻烦。

    这个时候只能退而求其次,用long long,说是退而求其次,但事实证明,long long在这道题中是够用的,而且后来在查阅资料的时候发现,大家遇事不决都会用long long这个整型数扛把子

数据类型取值范围
int2147483648~2147483647
long2147483648~2147483647
long long-9223372036854775808 ~ 9223372036854775807
  • 坑四:数值溢出

    当然这里的数值溢出指的不是指已知基数的字符串的数值会溢出,而是指在二分搜索过程中未知基数的字符串的值会在数制转换之后溢出为负数

    这样一来,就会导致原本应该是当前基数过大,应该使基数上界下移,但变为负数之后,判断条件以为基数过小了,就会调整使基数下界上移,这与我们二分搜索的初衷背道而驰

Details:


  • 1)基数下界

    其实,基数下界这个问题本身应该是不存在的,或者说不应该留存到二分搜索这一步来解决

    因为这个问题实质上就是:基数不能小于字符串中出现的最大字符 比如说:ToD(“1z1”,2)就不应该处理,因为得不到结果

    我去网上转了一圈,发现大家都是确定通过确定字符串中最大字符来确定下界来解决的,而他们的数值转换函数操作不合法(即:基数小于字符串中出现的最大字符对应的数字)的时候依然是按照36进制给出合法输出

    而我一开始就在数值转换函数里进行了参数合法性的判断,在参数不合法的时候返回值为-1

    当然,这也导致我在进行二分搜索的时候,判断溢出的条件会与别人不同,别人的判溢条件是进制转换后得到的数值小于0,而我的判溢条件是进制转换后的数值小于-1,由于一般溢出后的数值都是很小的负数,所以我这样做是行得通的

    至于后来在求上界的时候我发现还是要先求下界,那都是后话,后来我索性就将求下界过程整合进求上界的函数里去了

    测试数据

    输入:

    1 1z1 1 10

    输出:

    Impossible

  • 2)基数上界

    原本我以为进制的范围是2 ~ 36,毕竟用来代替数字的字符也就指定了36个,但是当很多测试用例跑不通时,我开始思考基数的上界。
    怎么确定基数上界呢?什么时候基数够?这就要看字符串里最大的那个字符了,这就确定了决定基数上界的一个因素:字符串中最大字符的值;那么,什么时候基数最大呢,当待定的数为1,该数进制就等于已知进制数的那个数的十进制值,这个时候基数达到最大。
    将**待定字符串基数够用(下界)**和 基数最大(最大为已知字符串值) 两种情况考虑进来,我们就不难得到基数的上界了,两者取其大即可

    测试数据

    输入:

    42949672 10 1 10

    输出:

    42949672

  • 3)进制转换

    该题需要实现从任意进制向十进制转换这样一个功能,其实c++库函数里本来就有strtol()函数可以实现这一点,但是我嫌弃它在遇到非法参数时拖泥带水,并不直接返回一个-1代表参数非法,而是将第一个非法字符之前的字符进行进制转换,这样的话我还是得去确定下界,再加上strtol()函数参数是char数组,我又想用string,那么就自己实现了一个,既然是自己实现,那就顺便做一个合法性判断,也就省得再去确定下界。

    那么既然要进行合法性判断,自然是将每一个字符对应的数值空间变成连续的才好进行判断,于是有了MAP()函数,将字符0 ~ 9 + a ~ z,映射到数字0 ~ 35,具体实现如下代码

Code1(未优化,暴力遍历,基数上下界固定(2 ~ 10000),得分24):

#include <bits/stdc++.h>
using namespace std;
int MAP(int i){
	if(i>='0'&&i<='9') return i-48;
	else if (i>='a'&&i<='z') return i-87;
	else return -1;
}
long  long ToD(string s,int radix){
	int current;
	int exp;
	long  long ans=0;
	for(int i=s.size()-1;i>=0;i--){
		current=MAP(s[i]);
		exp=s.size()-i-1;
		if(current>=0&&current<radix){
			ans+=current*pow(radix,exp);
		}else return -1;
	}
	return ans;
}

int main(){  
	string input[2];
	long  long value[2];
	int tag=0;
	int radix;	
	cin>>input[0]>>input[1]>>tag>>radix;
	value[tag-1]=ToD(input[tag-1],radix);
	int i;
	for(i=2;i<10000;i++){
		if(ToD(input[2/tag-1],i)==value[tag-1]){
			break;
		}
	}
	if(i<10000)
		cout<<i;
	else cout<<"Impossible";
	return 0;
}

Code2(优化过后,满分通过):

#include <bits/stdc++.h>
using namespace std;
long  long MAP(long  long i){
	if(i>='0'&&i<='9') return i-48;
	else if (i>='a'&&i<='z') return i-87;
	else return -1;
}
long  long ToD(string s,long  long radix){
	long  long current, exp, ans=0;
	for(int i=s.size()-1;i>=0;i--){
		current=MAP(s[i]);
		exp=s.size()-i-1;
		if(current>=0&&current<radix){
			ans+=current*pow(radix,exp);
		}else return -1;
	}
	return ans;
}
long long upperBound(string s, string s2, long long radix){
	long long current, high, max=-1;
	for(int i=0;i<s2.size();i++){
		current=MAP(s2[i]);
		if(max<current)
			max=current;
	}
	max++;
	high=max>ToD(s,radix) ? max : ToD(s,radix);
	return (high+1);
}

int main(){  
	string input[2];
	long  long value[2];
	int tag=0;
	long  long radix;	
	cin>>input[0]>>input[1]>>tag>>radix;
	value[tag-1]=ToD(input[tag-1],radix);
	//此处,tag-1代表被指定进制的数,2/tag-1代表的是进制待定的数
	//接下来是二分法
	//下界:因为我的进制转换函数以及二分过程做了修改,所以我的下界不需要指定,都从2开始即可
	//上界:
	int low=2;
	long long m, curVal, high=upperBound(input[tag-1],input[2/tag-1], radix);
	while(low<=high){
		m=(low+high)/2;
		curVal=ToD(input[2/tag-1],m);//进制待定那个数的当前值
		if(curVal==value[tag-1]){
			cout<<m;
			break;
		}else if(curVal>value[tag-1]||curVal<-1)	high=m-1;
		else	low=m+1;
	}
	if(low>high)
		cout<<"Impossible";
	return 0;
}

Summary and harvest

(for my current level)

  • link:复习二分法

    while(low<=high){
    	m=(low+high)/2;
    	value=getValue();
    	if(value==valueDesired){
    		cout<<m;
    		break;
    	}else if(cvalue>valueDesired)	high=m-1;
    	else	low=m+1;
    }
    
  • link:实现数制转换
    link:long int strtol(const char *nptr,char **endptr,int base);
    link:char *itoa (int value, char *str, int base );

  • 自行实现:

    long  long MAP(long  long i){
    	if(i>='0'&&i<='9') return i-48;
    	else if (i>='a'&&i<='z') return i-87;
    	else return -1;
    }
    long  long ToD(string s,long  long radix){
    	long  long current, exp, ans=0;
    	for(int i=s.size()-1;i>=0;i--){
    		current=MAP(s[i]);
    		exp=s.size()-i-1;
    		if(current>=0&&current<radix){
    			ans+=current*pow(radix,exp);
    		}else return -1;
    	}
    	return ans;
    }
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值