ACM-麦森数

    xiaoxiao2021-03-25  172

    问题描述

            形如 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

    解题思路

            第一个问题,输出 2p-1 有多少位。由于 2p-1 的各位数只可能是 2, 4, 6, 8 所以 2p-1 和 2p的位数相同。使用 C/C++标准库中在 math.h 里声明的,求以 10 为底的对数的函数 doublelog10(double x) 函数,就能轻松求得 2p-1 的位数。

            2p 的值需要用一种高效率的办法来算。         显然,对于任何 p>0,考虑 p 的二进制形式,则不难得到:         p = a020 +a121+ a222……+ an-12n-1 + 2n         这里, ai 要么是 1,要么是 0。         因而:

            计算 2p 的办法就是,先将结果的值设为 1,计算 21。如果 a0 值为 1,则结果乘以 21;计算 22,如果 a1 为 1,则结果乘以 22;计算 24,如果 a2 为 1,则结果乘以 24;……总之,第 i 步(i 从 0 到 n, an 是 1)就计算22i ,如果 ai 为 1,则结果就乘以22i 。每次由22i×22i就能算出22i+1 。由于 p 可能很大,所以上面的乘法都应该使用高精度计算。由于题目只要求输出 500 位,所以这些乘法都是只须算出末尾的 500 即可。

            在前面的高精度计算中,我们用数组来存放大整数,数组的一个元素对应于十进制大整数的一位。本题如果也这样做,就会超时。为了加快计算速度,可以用一个数组元素对应于大整数的 4 位,即将大整数表示为 10000 进制,而数组中的每一个元素就存放 10000 进制数的 1 位。例如,用 int 型数组 a 来存放整数 6373384,那么只需两个数组元素就可以了,a[0]=3384, a[1]=637。

            由于只要求结果的最后 500 位数字,所以我们不需要计算完整的结果,只需算出最后500 位即可。因为用每个数组元素存放十进制大整数的 4 位,所以本题中的数组最多只需要 125 个元素。

    参考程序

    #include <iostream> #include <cstdio> #include <memory.h> #include <cmath> #define LEN 125 using namespace std; void Multiply(int *a,int *b){ int i,j; int nCarry;//存放进位 int nTemp; 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++){ nTemp = c[i+j]+a[j]*b[i]+nCarry; c[i+j]=nTemp000; nCarry=nTemp/10000;//算出进位 } } memcpy(a,c,LEN*sizeof(int)); } int main(int argc, char *argv[]) { int i; int p; int anPow[LEN];//存放不断增长的 2 的次幂 int aResult[LEN];//存放最终结果的末 500 位 cin >> p; cout << (int)(p*log10(2))+1 <<endl; //下面将 2 的次幂初始化为 2^(2^0)(a^b 表示 a 的 b 次方) 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("d\nd",aResult[i]/100,aResult[i]0); }else{ printf("d",aResult[i]); if(i% == 0){ cout<<endl; } } } return 0; }         第13之20行:j 只要算到 LEN - i - 1,是因为 b[i]×a[j]的结果总是加到 c[i+j]上, i+j 大于等于 LEN 时, c[i+j]是不需要的,也不能要,否则 c 数组就越界了。b[i]×a[j]的结果总是要加到 c[i+j]上,此外还要再加上上次更新 c[i+j-1]时产生的进位。        第39之45行:每次执行循环都判断 a i(i 从 0 开始)的值是否为 1, 如果是,则将最终结果乘以2 2i 。接下来再由2 2i算出2 2i+1

           第48之57行:输出从万进制数的第 124 位开始,万进制数的每一位输出为十进制数的 4 位,每行只能输出 50 个十进制位,所以发现当 i% 等于 12 时,第 i 个万进制位会被折行输出,其对应的后两个十进制位会跑到下一行。

    转载请注明原文地址: https://ju.6miu.com/read-34489.html

    最新回复(0)