题目描述
形如2^ P-1的素数称为麦森数,这时P一定也是个素数。但反过来不一定,即如果P是个素数,2^P-1不一定也是素数。到1998年底,人们已找到了37个麦森数。最大的一个是P=3021377,它有909526位。麦森数有许多重要应用,它与完全数密切相关。
任务:从文件中输入P(1000<P<3100000),计算2^P-1的位数和最后500位数字(用十进制高精度数表示)
输入
文件中只包含一个整数P(1000<P<3100000)
输出
第一行:十进制高精度数2^P-1的位数。
第2-11行:十进制高精度数2^P-1的最后500位数字。(每行输出50位,共输出10行,不足500位时高位补0)
不必验证2^P-1与P是否为素数。
样例输入 Copy
1279
样例输出 Copy
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087
题目链接
题解
该题花费我很久的时间啦!功夫不负有心人,终于Ac掉啦且满满的收获!当我Ac那一刻,非常之激动呀!
言归正传,该题难度蛮大的需要高精度乘法和快速幂的知识,推荐博客:
大数高精度乘法
快速幂
当然理解高精度乘法和快速幂,该题变为So easy!
1,位数解法
首先我们来完成 第一行:十进制高精度数2^P-1的位数。即386位数的要求
我们举个例子来讲解:
比如 一个 100位的十六进制数a 转换为 2进制数,需要多少位?
400 位 = 100 * 4 ,因为大家都知道四个二进位表示一个十六进制位。为什么呢?
一个二进制为只能是 0 或 1 ,表示两种状态 ,一个十六进制位从 0 ~ 15,可以表示16种状态。 知 2 * 2 * 2 * 2 = 16 ,因此需要4 个二进位 来表示十六进制位(即 log 2 ^ 16 == 4)
再如 一个100位的十进制 a 转换成 3 进制数,需要多少位?
答案: 100 * log 3 ^ 10 约等于 209.59 即210位
位数肯定需要向上取整的
位数举一反三
一个十进制数a 需要多少位呢?
12 --> 2 位 log10 ^12 = 1.07918 向上取整2
99 --> 2位 log10 ^ 99 = 1.9956 向上取整2
100 --> 3位 log10 ^ 100 = 2 向上取整2
999 --> 3位 log10 ^ 999 = 2.9995 向上取整3
1001 --> 4位 log10 ^ 1001 =3.0004 向上取整4
…
因此我们可以解决first problem ,问题是2 ^ p - 1 的十进制有多少位?其实我们完全可以求2 ^ p 的十进制为有多少位,为什么可以呢?
想一想十进制数中啥情况的时候 减1 然后 位数变小呢? 是的,一般是10 , 100, 1000, 10000.。。。。。。,这些十的倍数只有减1 随之位数也会减1 ,其他数都不存在该情况
但是 你见过 哪个2^p数是十的倍数呀,我相信你是没有见过的,嘿嘿
最终位数等于 log 10 ^ (2^p) = p * log 10 ^ 2 ,故只需要一个式子即可求出位数
(math.h库函数提供log函数以e为底的对数函数, log10()函数以10为底的对数函数)
2,500位数的计算
其次我们来完成500位数的计算,此处我们需要快速幂来计算 2 ^ p 来防止超时. 其根本是long long 类型存不下2^p(p 较大时),需用使用高精度来计算大数的500位,此时可以体现大整数乘以大整数的高精度计算的优越性啦!!!
有小伙伴可能会纳闷? 2 ^p 是个非常大的数字,即使高精度计算使用的整型数组可能容纳不了吧!整型数组在内存开辟的空间也有限呀?
是的,没错,但是我们无需将2^p 的超大数的位数都存取来呀。因为有的数相乘是上千万甚至亿位,我们只需要按照题目的要求每次存取后500位即可。最终的结果也只需最后500位的啦
如下将会解决你的困惑!
此处选择任意的数字进行测试,最终的结果只取最后两位。
我们可以发现任意的两数字相乘的结果取最后两位数字,再用最后此两位数字乘以其他数字,再取最后结果的两位数字。依次类推,不断的取结果的最后两位。其实不难发现和先把所有数相乘起来再取最后两位数的结果相同。
故我们可以用此方法来求最后500位数字。在相乘的过程中不断的取结果的最后500数,其答案和计算2^p - 1总的超大数结果后取最后500位数字的 结果是一模一样的。
理解以上我讲述的重点地方,以及快速幂和大正数相乘的高精度后 理解代码So easy 的啦!!! 现在我们来看代码吧!
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <stdlib.h>
using namespace std;
const int MAX = 500;
struct Bignum//定义大数的结构体
{
int len;
int digit[MAX];
};
Bignum Init(int x)//将整形数值变为结构体
{
Bignum temp;
temp.len = 0;
do {
temp.digit[temp.len++] = x % 10;
x = x / 10;
} while (x);
return temp;
}
Bignum mul(Bignum x, Bignum y)//大数相乘
{
Bignum z;
memset(z.digit, 0, sizeof(z.digit));
//用memset来初始化z.digit的500位数字全为 0
if (x.len + y.len > MAX) z.len = MAX;//如果超过只取500位
else z.len = x.len + y.len;
int i, j;
for (i = 0; i < x.len; i++)
{
for (j = 0; j < y.len; j++)
{
if (i + j < MAX)//只取前500位
z.digit[i + j] += x.digit[i] * y.digit[j];
}
}
for (i = 0; i < z.len; i++)//进位,大整数乘法的进位
{
z.digit[i + 1] += z.digit[i] / 10;
z.digit[i] = z.digit[i] % 10;
}
while (z.digit[z.len - 1] == 0 && z.len > 0) z.len--;//去除前导 0
return z;//以大整数的形式返回
}
int main()
{
int p;
cin >> p;
//输入p次方
int m = ceil(p * log10(2));//m为向上取整的值
printf("%d\n", m);
//初始化res,和a,将变为数组形势
Bignum res = Init(1);
Bignum a = Init(2);
//快速幂
while (p > 0)
{
if (p % 2 == 1) res = mul(res , a);
a = mul(a, a);
p = p / 2;
}
res.digit[0]--;// 最终结果将大数减1,即res数组的0位减1
//------------------------------------------------------
//每按50行进行打印
int k = 0;
for (int i = MAX - 1; i >= 0; i--)
{
cout << res.digit[i];
k++;
if (k % 50 == 0) cout << endl;
}
return 0;
}
结语
当然本人能力有限不一定讲的很清楚。若看讲解仍旧一头雾水,此处附上视频链接https://www.bilibili.com/video/BV17f4y127D9
推荐看一看,有助于该题的理解,哈哈哈