洛谷上的P1045 [NOIP2003 普及组] 麦森数
题目提供者:CCF_NOI
看到这个题目大家可能发现如果P为最大值的时候,可能要创建一个1000005位的数组才可能装的下这个大数,虽然我们也可以改变一下大数的规则,每个数组元素装更多位的数字,不局限于0~9。
但是这道题可以看成两个部分,第一是求出2的p次方的位数,第二是求出最后500位数,这两部分其实没有半毛钱关系。
第一部分:求出2的p次方的位数
我们想想位数的表示,想想科学计数法,perfect!10的n次方就可以视为n+1位数,那我们拥有的数字是2的p次方-1,但是这个的位数和-1没有关系,所以可以看成是求2的p次方的位数。那么现在的问题就是如何把2的p次方变成10的n次方了,简单思考发现,2的p次方=10的log10(2的p次方)的次方,所以位数可以表示为int k = log10(2的p次方)+1 = p*log10(2)+1,即向下取整。
第二部分:求出最后500位数
一开始我觉得还很简单,就一个大数乘法模拟,结果后来时间复杂度太大了,过不了。。。后来仔细看看,发现其中包含了两个大数相乘,快速幂思想,所以我在这里顺便讲一下快速幂和两个大数相乘需要注意的地方吧。
快速幂
一开始我们有个2的10次方,即2*2*2*2*2*2*2*2*2*2,容易看出它等价于4*4*4*4*4即(2*2)*(2*2)*(2*2)*(2*2)*(2*2),(2*2)的5次方,分析发现,任何n的p次方,当指数p为偶数时,n的p次方等于(n的平方)的(p/2)次方。
而当p为奇数时,例如4的五次方就=4的4次方*4,只需要先用一个变量res把这个离出来的底数base的一次方收集好就行,res *= base;
当p等于1时,它也为奇数,p的1次方=p的0次方+p的一次方,而p被收集起来,最后只需要把res输出就是p的n次方的答案了。至于快速幂模板在后文代码演示有给出,就不打出来了。
两个大数相乘
首先要说明的是为什么强调只用求出最后500位上的数,因为位数在500位以上的数相乘是不影响500位以下的数的,例如123*456,最后十位数的结果是不关百位数的事的,所以两个数相乘,结果位数超过500位的数字直接丢掉就行。
其次就是要讲一下两个大数是如何相乘的呢?用a={0, 3, 2, 1}和b={0, 6,5,4}举例,即123*456,先用创建一个空数组s[]={0,0,0, 0},先用6乘以3等于18加到s[1]上,6*2=12加在s[2]上,6*1=6加在s[3]上,现在s={0, 18, 12, 6}, 剩下的5, 4也同理操作,但是呢,5*3就要加在s[2]上了, 因为一个十位数乘一个个位数,答案是一个十位数。
由此大家可以发现用i,j两层循环遍历a,b数组,s[i+j-1] = a[i]*b[j](这也是为什么从1开始而不是从0开始遍历数组的原因)
完成相乘之后再用一个循环解决进位问题,这就是两个大数相乘的基本步骤。
接下来是题目代码演示,有注释的哟!
...
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int s[505] = {0}; //用来存快速幂的底数
int temp[505] = {0};
int result[505] = {0}; //用来存储最后500位的数
void cheng1() //把指数为奇数时分离出来的底数的一次方收集好
{
memset(temp, 0, sizeof(temp));
for (int i = 1; i <= 500; i++)
{
for (int j = 1; j <= 500; j++)
{
//i,j都从1开始好算乘法位数, 超过500位的就丢掉
if (i+j-1 > 500) break;
//先计算每一位上的值(不进位)
temp[i+j-1] += result[i]*s[j];
}
}
for (int i = 1; i <= 500; i+=1)
{
temp[i+1] += temp[i]/10; //单独进位
temp[i] %= 10;
}
memcpy(result, temp, sizeof(result)); //把temp赋值给result
}
void cheng2() //底数的平方,就是自己乘自己
{
memset(temp, 0, sizeof(temp));
for (int i = 1; i < 501; i++)
{
for (int j = 1; j < 501; j++)
{
if (i+j-1 > 500) break;
temp[i+j-1] += s[i]*s[j];
}
}
for (int i = 1; i < 501; i++)
{
temp[i+1] += temp[i]/10;
emp[i] %= 10;
}
memcpy(s, temp, sizeof(s));
}
int main()
{
int p;
cin >> p;
//求位数
int k;
//k = log10(2的p次方) = p*log10(2)
k = p*log10(2) + 1; //整数自动向下取整, 10的1次方是两位数
out << k << "\n";
// 求最后500位数字
result[1] = 1;
s[1] = 2; //初始化第一个底数为2
while (p > 0) //快速幂模板
{
if (p % 2 != 0)
{
cheng1();
}
p /= 2;
cheng2();
}
result[1] -= 1;
for (int i = 500; i > 0; i--)
{
if (i%50 == 0 && i != 500)printf("\n");
printf("%d", result[i]);
}
}
...