1010 Radix(绝妙二分题)

解题思路
这是一道较难的题目,题解是二分。二分的题目我是真看不出来。
题目给出两个数 N1 , N2 , 再给出一个 tag 和 radix , 给出的是第tag个数的进制。题目问两个数是否相等,实际上我们可以将两者转换为十进制,看他们是否相等,比如 30 110 1 4 ,30在这里是4进制数,先转换为10进制,就是3 × 4 + 0 × 1 = 12, 然后找到一个进制使得110在这个进制下转换为十进制的数值等于12。
可以将这个求解问题转化为判定问题,即我猜一个数,这个数在所有可能的解的区间内,然后看这个数是否满足我的要求,由于该问题具有单调性,即一个固定的数,其进制越大,转化的数值也越大,不断二分确定解的区间。
这里要注意一点 由于输入示例中存在字符,所以要以字符串的形式输入,然后把已知进制的数的十进制数求出来,作为目标值,然后进行二分。
- 同时,如果数中有字符,每个字符是独立的一位。
ac✅️代码
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
typedef long long ll;
ll GetCharValue(char c)
{
if(isdigit(c))
{
return c - '0';
}
return c - 'a' + 10;//以a为起点,数值是10,所以要加10
}
//猜一个进制,将N2转化为十进制
ll CalculateValue(string s,ll radix )
{
ll res = 0;
for(char c : s)
{
ll val = GetCharValue(c);
//相乘的过程可能会超过 ll 的范围,所以要用(double),一旦乘法溢出,可能会变成负数.
//由于本题给出的进制最大是36,而N1的位数最多是十位,所以target值最大能取到36^10
//36略大于2^5, 就是2的50次方,又因为2的10次方约等于10的三次方,所以大概在10的15次方 左右,所以选取一个合理的2e18
if((double)res * radix + val > 2e18 )
{
return -1;
}
res = res * radix + val;
}
return res;
}
int main()
{
string n1,n2;
ll tag,radix;
if(!(cin>>n1>>n2>>tag>>radix))
{
return 0;
}
//统一处理,让N2成为未确定进制的数
if(tag == 2) swap(n1,n2);
//确定target值
ll target = 0;
for(char c : n1)
{
target = target * radix + GetCharValue(c);
}
//要确定N2的进制边界,按它给出的数确定它的最小可能进制,比如五进制的数里面不可能有6。11进制的数不可能有c
ll low = 0;
for(char c : n2)
{
low = max(low,GetCharValue(c));
}
//加一才是最小边界,因为进制大于所有位上的数
low ++;
//这一步特别妙,实在是高人,high范围确定的太准了
ll high = max(low , target) + 1;
/*
在极端情况下,比如target值达到最大,而N2是10,其转化为十进制为1×进制 ,
这样其要达到target,进制也要很大,所以取target+1让它足够大
这样右边界我们就确定了,然后确定左边界,一个数其最小边界就是我们上面说的low,
由于这里求的是high,low已经定了,high可以大一些,所以要加一,当然这里不加一也能ac。
这里加一其实有点说法的,二分区间长度一般来说要大于等于2,嗯,差不多这样就行了。
*/
ll ans = -1;
while(low <= high)
{
ll mid = low + (high - low) / 2;
ll val = CalculateValue(n2 , mid);
if(val == target)
{
ans = mid ;
//这里找到答案了,但是不能break,因为题目要求如果存在多解就输出最小值,所以还要在左边去检查一遍
high = mid - 1;
}
else if(val == -1 || val > target)
{
high = mid - 1;
}
else low = mid + 1;
}
if(ans == -1) cout<<"Impossible"<<endl;
else cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号