麦森数
问题描述
形如 2p-1的素数称为麦森数,这时 P一定也是个素数。但反过来不一定,即如果 P是个素数。2p-1不一定也是素数。到 1998年底,人们已找到了 37个麦森数。昀大的一个是 P=3021377,它有 909526位。麦森数有许多重要应用,它与完全数密切相关。
你的任务:输入 P (1000<P<3100000) ,计算 2p-1的位数和昀后 500位数字(用十进制高精度数表示)
输入数据
只包含一个整数 P(1000<P<3100000)
输出要求
第 1行:十进制高精度数 2p-1的位数。第 2-11行:十进制高精度数 2p-1的昀后 500位数字。(每行输出 50位,共输出 10行,不足 500位时高位补 0)
不必验证 2p-1与 P是否为素数。
输入样例
1279
输出样例
386
00000000000000000000000000000000000000000000000000 00000000000000000000000000000000000000000000000000 00000000000000104079321946643990819252403273640855 38615262247266704805319112350403608059673360298012 23944173232418484242161395428100779138356624832346 49081399066056773207629241295093892203457731833496 61583550472959420547689811211693677147548478866962 50138443826029173234888531116082853841658502825560 46662248318909188018470682222031405210266984354887 32958028878050869736186900714720710555703168729087
解题思路
由于只要求结果的昀后 500位数字,所以我们不需要计算完整的结果,只需算出昀后 500位即可。因为用每个数组元素存放十进制大整数的 4位,所以本题中的数组昀多只需要 125个元素。
参考程序 (改编自学生提交的程序 ):
#include <stdio.h>
#include <memory.h>
#define LEN 125 //每数组元素存放十进制的 4位,因此数组昀多只要 125个元素即可。
#include <math.h>
/* Multiply函数功能是计算高精度乘法 a * b
结果的末 500位放在 a中
*/
void Multiply(int* a, int* b)
{
int i, j;
int nCarry; //存放进位
int nTmp;
int c[LEN]; //存放结果的末 500位
memset(c, 0, sizeof(int) * LEN);
for (i=0;i<LEN;i++) {
nCarry=0;
for (j=0;j<LEN-i;j++) {
nTmp=c[i+j]+a[j]*b[i]+nCarry;
c[i+j]=nTmp%10000;
nCarry=nTmp/10000;
}
}
memcpy( a, c, LEN*sizeof(int));
}
int main()
{
int i;
int p;
int anPow[LEN]; //存放不断增长的 2的次幂
int aResult[LEN]; //存放昀终结果的末 500位
scanf("%d", & p);
printf("%d\n", (int)(p*log10(2))+1);
//下面将 2的次幂初始化为 2^(2^0)(a^b表示 a的 b次方),
//昀终结果初始化为 1
anPow[0]=2;
aResult[0]=1;
for (i=1;i<LEN;i++) {
anPow[i]=0;
aResult[i]=0;
}
//下面计算 2的 p次方
while (p>0) { // p = 0则说明 p中的有效位都用过了,不需再算下去
if ( p & 1 ) //判断此时 p中昀低位是否为 1
Multiply(aResult, anPow);
p>>=1;
Multiply(anPow, anPow);
}
aResult[0]--; //2的 p次方算出后减 1
//输出结果
for (i=LEN-1;i>=0;i--) {
if (i%25==12)
printf("%02d\n%02d", aResult[i]/100,
aResult[i]%100);
else {
printf("%04d", aResult[i]);
if (i%25==0)
printf("\n");
}
}
return 0;
}
语句 17:j只要算到 LEN - i - 1,是因为 b[i]×a[j]的结果总是加到 c[i+j]上,i+j大于等于 LEN时,c[i+j]是不需要的,也不能要,否则 c数组就越界了。
语句 18: b[i]×a[j]的结果总是要加到 c[i+j]上,此外还要再加上上次更新 c[i+j-1]时产生
的进位。
语句 19:由于 c中的每一元素代表 10000进制数的 1位,所以 c[i+j]的值不能超过 10000。
语句 20: 算出进位。语句 43到语句 48,每次执行循环都判断 ai(i从 0开始)的值是否为 1,如果是,则将最终结果乘以 。接下来再由 算出 。
语句 54: 输出从万进制数的第 124位开始,万进制数的每一位输出为十进制数的 4位,每行只能输出 50个十进制位,所以发现当 i%25等于 12时,第 i个万进制位会被折行输出,其对应的后两个十进制位会跑到下一行。
语句 55: “%02d”表示输出一个整数,当输出位数不足 2位的时候,用前导 0补足到
2位。本行将一个万进制位分两半折行输出。语句 58: 将一个万进制位以十进制形式输出,用前导 0确保输出宽度是 4个字符。语句 59: 满足条件的话就该换行了。常见问题问题一:没有想到用数学公式和库函数可以直接计算结果位数,而是用其他办法大费周
折。问题二:试图用昀简单的办法,做 p次乘以 2的操作,结果严重超时。问题三:没有对数据规模有足够估计,用数组表示十进制大整数而非万进制数,结果超
时。
思考题 7.4 :本题在数组中存储万进制大整数以加快速度。如果存储的是十万进制数,
岂不更快?而且输出时十万进制数的一位正好对应于十进制的 5位,计算折行也会方便很
多。这种想法成立吗?这么写会真的会更方便吗?