(快速幂算法+高精度)洛谷P1045 麦森数

前言

  故事的最后,让我们以一道十分经典的题目——《麦森数》来结尾。接受现实吧,总会有我们没准备过的高精度运算出现。我们固然可以提前把高精度的快速幂模板也准备好,但是总会有百密一疏的时候,即使出现过一种没见过的高精度运算类型,也要做出合理的应对。

题目概述

在这里插入图片描述

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 2525,如果 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 2p1的位数是等于 2 p 2^p 2p次方的位数的。为什么呢?因为 2 p 2^p 2p的末尾不会是0,所以不可能出现说减去1把位数给减少了。那为什么最后一位不可能是0?因为最后一位是0,那么 2 p − 1 2^{p-1} 2p1末尾就得是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=plog102+1

4.最后就是输出格式了,记得最后一位要减去1,不用考虑借位,理由同求长度分析。怕换行用i搞不清楚搞不清楚的就另设一个计数器,满50换一次。

5.不会高精度乘以高精度的,可以看看这题。A*B问题,高精度乘以高精度
6.最后这道题目就是想说,高精度虽然是模板性质比较强的一种题型,但是也不能光指望模板,要真的深刻理解高精度“模拟人工运算+进位”的手法,这样出现没背过的运算时候也可以自己临时实现。

文末广告

  学习算法和数据结构真的是个很累的过程,不会做只能求助于题解。 因为写代码这个东西基本上是千人千面。同时网络上搜到的题解很多要么用到的是自己还没学到的知识,看不懂;要么内核过于简陋,只能糊弄当前题目,不具有普适性。

  如果你是一个喜欢做洛谷,ACwing和PTA的题目的同学,欢迎关注我的博客,我主要在这三个平台上做题,认为有价值和有难度的题目我会写题解发布出来。
TreeTraverler的往期文章

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

102101141高孙炜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值