前言
故事的最后,让我们以一道十分经典的题目——《麦森数》来结尾。接受现实吧,总会有我们没准备过的高精度运算出现。我们固然可以提前把高精度的快速幂模板也准备好,但是总会有百密一疏的时候,即使出现过一种没见过的高精度运算类型,也要做出合理的应对。
题目概述
AC代码
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define maxn 1002
int mid[maxn],base[maxn],ans[maxn];
void qpower()//替代低精度快速幂算法中的相乘部分
{
memset(mid,0,sizeof(mid));
for(int i=1;i<=500;++i)
for(int j=1;j<=500;++j)
mid[i+j-1]+=ans[i]*base[j];
for(int i=1;i<=500;++i)
mid[i+1]+=mid[i]/10,mid[i]%=10;
memcpy(ans,mid,sizeof(mid));
}
void grow()//替代低精度快速幂算法中的base自平方部分
{
memset(mid,0,sizeof(mid));
for(int i=1;i<=500;++i)
for(int j=1;j<=500;++j)
mid[i+j-1]+=base[i]*base[j];
for(int i=1;i<=500;++i)
mid[i+1]+=mid[i]/10,mid[i]%=10;
memcpy(base,mid,sizeof(base));
}
int main()
{
int p;
scanf("%d",&p);
printf("%d\n",(int)(log10(2)*p+1));
ans[1]=1,base[1]=2;//ans存的是答案,base存的是快速幂算法里已经积累的次数
while(p>0)//快速幂
{
if(p&1)
qpower();
grow();
p>>=1;
}
int sum=0;
ans[1]-=1;//2的次方数末尾不会是0,可以直接减去1不用考虑借位的问题
//(因为如果要出来0前一个末尾得是5,这显然不可能)
for(int i=500;i>=1;--i)
{
printf("%d",ans[i]);
++sum;
if(sum%50==0)
printf("\n");
}
return 0;
}
分析思路
1.首先可能会想到用朴素的做法,因为大整数类里是有存储长度的,然后把2乘p次,但是这样有两个问题,一个是时间复杂度达到O(n),必然要TLE,另一个是题目暗示最后的位数可能会很大,数组也没有那么多位数,所以这题不能再使用Bigint模板了,而要自己实现一个更高效的乘方运算。并且只需要后五百位数字。所以啊,刚开始学要背好模板,但是题目越往后做,反而不能再被模板限制,有点“无招胜有招”那味。
2.快速幂算法是如何实现的?比如 2 10 2^{10} 210,朴素的做法是乘10次,但是我们需要吗?想想我们自己计算的时候,我们是直接用 2 5 ∗ 2 5 2^5*2^5 25∗25,如果 2 5 2^5 25也不会呢?那就考虑用更小的次方。快速幂其实类似于这样的思想,从小的次方开始指数扩大,这样就大大降低了复杂度。事实上,快速幂算法的时间复杂度是O( l o g n log_n logn)的。这个在很多题目里都可以使用来做优化。一般地,对于一个低精度快速幂 a b a^b ab(因为幂运算会很大,所以模板还加了取余k的操作),有以下的做法:
#define ll long long
ll qpower(ll a,ll b)
{
ll base=a,ans=1;
while(b>0)
{
if(b&1)
{
ans*=base;
ans%=k;
}
base*=base;
base%=k;
b>>=1;
}
return ans;
}
其中"b>>1"实际是通过二进制来检查有没有当前位可用。也就是说实际上是这样做的,先检查有没有 a 1 a^1 a1,再检查有没有 a 2 a^2 a2,再检查有没有 a 4 . . . a^4... a4... 有的话就把他们相乘。结束的条件是b在二进制下全都是0了,也就是全部分解完毕。那么对于高精度的呢?这就需要用到高精度乘高精度做法了,对应的替换写在注释行里了,这里不再赘述。
3.那么位数如何计算呢(不用高精度Bigint存储长度的情况下)?首先,
2
p
−
1
2^p-1
2p−1的位数是等于
2
p
2^p
2p次方的位数的。为什么呢?因为
2
p
2^p
2p的末尾不会是0,所以不可能出现说减去1把位数给减少了。那为什么最后一位不可能是0?因为最后一位是0,那么
2
p
−
1
2^{p-1}
2p−1末尾就得是5,这与它是偶数相矛盾。其次我们知道,
1
0
n
10^n
10n有
n
+
1
n+1
n+1位。那么,有如下的变形:(用到了高中的指对变换方法)
2
p
=
1
0
l
o
g
10
2
p
2^p={10^{log_{10}2}}^p
2p=10log102p
所以,它的位数就是:
l
o
g
10
2
p
+
1
=
p
∗
l
o
g
10
2
+
1
log_{10}2^p+1=p*log_{10}2+1
log102p+1=p∗log102+1
4.最后就是输出格式了,记得最后一位要减去1,不用考虑借位,理由同求长度分析。怕换行用i搞不清楚搞不清楚的就另设一个计数器,满50换一次。
5.不会高精度乘以高精度的,可以看看这题。A*B问题,高精度乘以高精度
6.最后这道题目就是想说,高精度虽然是模板性质比较强的一种题型,但是也不能光指望模板,要真的深刻理解高精度“模拟人工运算+进位”的手法,这样出现没背过的运算时候也可以自己临时实现。
文末广告
学习算法和数据结构真的是个很累的过程,不会做只能求助于题解。 因为写代码这个东西基本上是千人千面。同时网络上搜到的题解很多要么用到的是自己还没学到的知识,看不懂;要么内核过于简陋,只能糊弄当前题目,不具有普适性。
如果你是一个喜欢做洛谷,ACwing和PTA的题目的同学,欢迎关注我的博客,我主要在这三个平台上做题,认为有价值和有难度的题目我会写题解发布出来。
TreeTraverler的往期文章