49:计算对数
Description
给定两个正整数a(a>1)和b。可以知道一定存在整数x,使得
x <= logab < x + 1 或者 ax<= b < ax+1
请计算x。
Input
两行,第一行是a,第二行是b。每个整数均不超过100位。
Output
一行,即对应的x。输入数据保证x不大于20。
Sample Input
10000
1000000000001
Sample Output
3
题目解析
这道题有两个考点——大整数乘法、二分查找。(不知道对数是什么的同学戳一下 ->百度百科-对数)
首先我们需要知道如何用大整数处理对数。用目前知道的方法,用大整数是很难实现的,于是我们必须要换一种方法。
作者最先想到的方法是用乘方代替对数。但是这样并不能一次就确定答案,而是要不断向答案靠近——这直接指向了二分查找。我们把答案(ans)设置为不断接近的目标,又因为输入的大整数a,b都是大于1的正整数,因此答案的最小值是0,此时的结果是最小的正整数1,答案的最大值不需要我们确定,题目已经给出,是20。那么我们可以设置两个边界变量right,left,表示当前情况 left<=ans<=right ,在二分查找中不断缩小边界以确定最终答案。
先不管乘方计算,我们来看看二分查找。首先是一个mid变量储存中间值,作为乘方计算的指数。计算出乘方后,我们需要改变边界的值。因为我们计算的底数一定是正整数,那么一般来说(除了1)指数越大,则幂越大。我们也可以反过来想,也就是说如果乘方结果大于b,则说明指数过大,相反,则是指数小了一些。换入二分查找则是如果计算结果(Set)比b大,则将右边界(right)改为mid,反之则左边界(left)改为mid。
再回过头来看乘方运算。大家都知道这样一个式子:
a^n=a*a*a*...*a*a(n个a)
那么我们可以理解为a乘n次。那么就可以用一个for循环,循环变量(i)从1开始,循环mid次,每次对上一次得出的结果作一次乘法运算。最后的结果就是乘方结果了。别忘了把储存结果的大整数初始化为1!
但是有同学会担心内存问题,其实这种担心是确实存在的。题目要求的大整数最大有100位,而最大的指数是20,则运算结果最大会有10^40位…作者也心存疑虑,BUT…提交了,哈哈哈,AC了。这数据是真心水,大家开10005位的大整数就可以了。
题外话
NOI就要到了,老师突发奇想让我们刷1.13里的题,感觉还不错。作者多久没有看前面的题了,发现以前不好做的题现在一天就做了好几道,也算是与时俱进吧。在这里也祝各位向往着NOI的同学能够天天进步,与时俱进,在NOI上绽放光彩!(作者也会去的)
程序样例
1次AC的水数据,二分查找+大整数计算:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct Cheak
{
int num[10005],len;
Cheak()
{
memset(num,0,sizeof(num));
len=0;
}
};
char sa[105],sb[105];
Cheak Algorithm(Cheak Setting1,Cheak Setting2)
{
Cheak Over;
for(int i=1;i<=Setting1.len;i++)
{
int op=0;
for(int j=1;j<=Setting2.len;j++)
{
Over.num[i+j-1]=Setting1.num[i]*Setting2.num[j]+op+Over.num[i+j-1];
op=Over.num[i+j-1]/10;
Over.num[i+j-1]%=10;
}
Over.num[i+Setting2.len]=op;
}
Over.len=Setting1.len+Setting2.len;
while(Over.num[Over.len]==0 && Over.len>1)
Over.len--;
return Over;
}
int Point(Cheak a,Cheak b)
{
int maxlen=max(a.len,b.len);
for(int i=maxlen;i>=1;i--)
{
if(a.num[i]>b.num[i])
return 1;
if(a.num[i]<b.num[i])
return -1;
}
return 0;
}
int main()
{
Cheak a,b;
scanf("%s%s",sa,sb);
a.len=strlen(sa);b.len=strlen(sb);
for(int i=1,j=a.len-1;i<=a.len;i++,j--)
a.num[i]=sa[j]-'0';
for(int i=1,j=b.len-1;i<=b.len;i++,j--)
b.num[i]=sb[j]-'0';
int left=0,right=20;
while(left+1<right)
{
int mid=(left+right)/2;
Cheak Set;
Set.num[1]=1;
Set.len=1;
for(int i=1;i<=mid;i++)
Set=Algorithm(a,Set);
switch(Point(Set,b))
{
case 1: right=mid;break;
case 0: printf("%d\n",mid);return 0;
case -1: left=mid;break;
}
}
printf("%d\n",left);
return 0;
}