PAT1010 进制匹配问题
题目简介:
给出两个数字N1和N2,并给出其中一个数字的进制,找到一个合适的进制,使得另一个数字用该进制的情况下,使的两个数字的值相等。如果找不到这个进制,则输出Impossible。
注:为了方便描述,我们始终让N1存储已知进制的数字,让N2存储未知进制的数字,即如果tag==2,仅需要交换N1和N2即可。
题目分析:
该问题有几个关键点需要处理。首先是N1和N2都要转换成10进制进行比较;其次是N1和N2转换进制的时候,需要处理溢出的情况;再次,需要确定N2进制的上限和下限。
解决思路:
N1转换成10进制利用进制转化算法即可,按照题意来看,N1的10进制应该可以用long long
表示最多10位的情况(反正这么做是可以通过的,如果真的无法表示的话,那么题目就过于复杂了!)。
对于N2进制区间确定的问题,可以这么理解:
N2进制的下限肯定是N2中出现的最大的数字(包括字母表示的)加1,在这里假设为low
;
N2进制的上限可以理解为N1+1和low
中较大的那个,在这里假设为high
,可以这么想,如果进制的上限high>low
且high>N1
,那么只要N2有一个非0位,则N2>N1
是必然的,这就没有比较意义了。这也是本题思路上的一个卡点。同理,low>N1
的情况,为了保证数字是有意义的,只能选择low
为上限了(此时上下限相等),不过这种情况除非都是0,否则肯定是Impossible的。。
具体算法:
1. 快速转换映射 Map[]
为了后期进制转化时不用在判断数字还是字母,在这里直接使用一个Map
数组映射,具体直接参考init
函数的操作,很好理解。
2. k进制转化10进制算法 num2decimal()
核心思想就是一行代码,已经在代码中标出来了。不过本题要添加一句是否溢出或者超过进制上界的判断(只是针对N2的),如果这种情况发生了,说明N2在当前某进制下的数值肯定大于N1了,再继续向下判断没有任何意义了,相当于一个剪枝操作。
3. 二分查找算法 BSearch()
由于进制的范围未知,因此采取二分查找,来保证渐进复杂度满足要求。因为所有可能的区间范围是
[2,263−1]
[
2
,
2
63
−
1
]
,即long long
的范围。 由于进制是有序的,所以使用二分查找,顺序查找会超时!自己写二分的时候,注意区间的控制,个人习惯是前开后闭。
cmp比较函数
用来比较数据大小的。因为考虑到N2溢出的情况,所以为了简化BSearch的代码思路,专门写了一个这个函数。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL INF = (1LL << 63) - 1;
LL Map[130]; // 快速映射使用的,提供了一种解决问题的思路
void init() {
for(char c = '0'; c <= '9'; ++c) {
Map[int(c)] = c - '0';
}
for(char c = 'a'; c <= 'z'; ++c) {
Map[int(c)] = c - 'a' + 10;
}
}
// 把r进制的num转换成10进制,t是上界
LL num2decimal(const string& num, LL r, LL t) {
LL sum = 0;
int l = num.length();
for(int i = 0; i < l; ++i) {
if(sum < 0 || sum > t) { // 溢出或者超过上界,返回-1
return -1;
}
sum = sum * r + Map[num[i]]; // 转换函数的核心思想
}
return sum;
}
LL max_digit(const string& num) {
LL res = -1;
for(int i = 0; i < num.length(); ++i) {
if(Map[int(num[i])] > res) {
res = Map[num[i]];
}
}
return res;
}
int cmp(const string& N2, LL radix, LL t) {
LL num = num2decimal(N2, radix, t);
if(num < 0) { // 溢出肯定是num大
return 1;
}
if(t > num) { // t比较大,确定t是上界
return -1;
} else if(t == num) { // 相等的情况
return 0;
} else {
return 1; // num比较大
}
}
// 由于进制的范围未知,因此采取二分查找,来保证渐进复杂度满足要求
LL BSearch(const string& N2, LL low, LL high, LL t) {
LL mid;
while(low < high) {
mid = (low + high) / 2;
int flag = cmp(N2, mid, t);
if(flag == 0) { // 正好是相等的情况
return mid;
} else if(flag == -1) { // mid进制偏小
low = mid + 1;
} else { // mid进制偏大
high = mid;
}
}
return -1; // 找不到
}
int main() {
init();
string N1, N2;
LL tag, radix;
cin >> N1 >> N2 >> tag >> radix;
if(tag == 2) { // 始终让N1是已知的
N1.swap(N2);
}
LL t = num2decimal(N1, radix, INF);
LL low = max_digit(N2);
LL high = max(low, t) + 1;
LL ans = BSearch(N2, low, high + 1, t);
if(ans == -1) {
cout << "Impossible" << endl;
} else {
cout << ans << endl;
}
return 0;
}
总结:
学习了进制转换和二分搜索,个人觉得比较好的一道题目。不过感觉题目有个迷惑人的地方:* If the solution is not unique, output the smallest possible radix. * 但是这里并没判断就能通过了。可能是只会有一个正确的进制解。