洛谷上的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]);

  } 

 } 

... 

posted @ 2021-04-19 22:34  白缺  阅读(123)  评论(0编辑  收藏  举报