C语言程序设计100例之(69):麦森数

例69   麦森数

问题描述

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

        (1)编程思路。

        由于2p的个位数字只可能是2、4、6、8,所以2p-1 和2p的位数相同。利用表达式(int)(p*log10(2))+1)可以轻松求得2p-1 的位数。

        2p 的计算采用快速幂运算来完成。关于快速幂运算请参考前文“C语言程序设计100例之(41):快速幂运算”(https://www.cnblogs.com/cs-whut/p/15757118.html)。

        由于P值较大,快速幂运算中的乘法都应该使用高精度计算。由于题目只要求输出后面的500位,所以乘法只须算出末尾的500位即可。

        为了加快计算速度,采用万进制表示一个大整数,即用一个数组元素来保存大整数的4 位。例如,用int 型数组a来存放大整数1234567890,那么只需3个数组元素就可以了,

a[0]=7890,a[1]=3456,a[2]=12。

(2)源程序。

#include <stdio.h>

#include <math.h>

#include <string.h>

#define LEN 125 // 采用万进制,因此保存500位只要125个数组元素

void multiply(int *a, int *b)

{

    int c[LEN];

    memset(c, 0, sizeof(c));

    int i, j;

    int carry,tmp;

    for (i=0;i<LEN;i++)

    {

        carry=0;

        for (j=0;j<LEN-i;j++)

        {

            tmp=c[i+j]+a[j]*b[i]+carry;

            c[i+j]=tmp%10000;

            carry=tmp/10000;

        }

    }

    for (i=0;i<LEN;i++)

        a[i]=c[i];

}

int main()

{

    int i,p;

    int p2m[LEN];   // 存放不断增长的2的m次幂

    int ans[LEN];

    scanf("%d", &p);

    printf("%d\n", (int)(p*log10(2))+1);

    memset(p2m, 0, sizeof(p2m));

    memset(ans, 0, sizeof(ans));

    p2m[0]=2;

    ans[0]=1;

    while (p>0)    // 采用快速幂运算计算2的p次方

    {

        if ( p & 1 )

           multiply(ans, p2m);

        p>>=1;

        multiply(p2m, p2m);

    }

    ans[0]--;

    for (i=LEN-1;i>=0;i--)

    {

        if (i%25==12)

           printf("%02d\n%02d", ans[i]/100,ans[i]%100);

        else

        {

           printf("%04d", ans[i]);

           if (i%25==0) printf("\n");

        }

    }

    return 0;

}

习题69

69-1  组合数

问题描述

给定正整数N和M(5 ≤ M ≤ N ≤ 100),计算

 C语言程序设计100例之(69):麦森数    的值。

输入

输入包括若干组测试用例,每组测试用例占1行,包含两个正整数N和M,0 0表示输入结束。

输出

输出对应的C值,并按如下格式输出:[N] things taken [M] at a time is [C] exactly.

输入样例

100 6

20 5

18 6

0 0

输出样例

100 things taken 6 at a time is 1192052400 exactly.

20 things taken 5 at a time is 15504 exactly.

18 things taken 6 at a time is 18564 exactly.

         (1)编程思路。

        组合数C(M, N)可化简为N*(N-1)*…*(N-M+1) /(M*(M-1)*…*2*1),因此本题实际是一个大整数和普通整数进行乘法运算,一个大整数和普通整数进行除法运算两种操作。

        定义全局数组int a[200]保存所求的组合数,全局变量len保存所求组合数的位数。初始时,a[0]=1,len=1。

        编写函数void mul(int b)完成大整数a与整数b相乘,乘积仍保存在数组a中,函数void div(int b)完成大整数a除以整数b,商也保存在数组a中。

       (2)源程序。

#include <stdio.h>

int a[200];

int len = 0;     //  len保存高精度数当前的位数

void mul(int b)  // 高精度乘法,a为被乘数,b为乘数

{

       int i,cf=0;

       for (i=0; i<200; i++)

      {

              a[i] = a[i]*b+cf;

              cf = a[i]/10;

              a[i]%= 10;

       }

       for (i=len; i<200; i++)    // 更新位数len

              if(a[i] != 0) len++;

}

void div(int b)    // 高精度除法,a为被除数,b为除数

{

       int last = 0;   // 余数

       int i;

       for (i=len-1; i>=0; i--)

      {

              int cur = last*10+a[i];

              if (cur<b)

              {

                     a[i] = 0;     // 将商还是保存在被除数数组a中

                     last = cur;

              }

              else

              {

                     a[i] = cur/b;

                     last = cur%b;

              }

       }

       for (i=len-1; i>=0; i--)  // 更新位数len

              if (a[i]!=0) break;

       len=i+1;

}

int main()

{

       int n, m;

       while (scanf("%d%d", &n, &m) && (n||m))

      {

              int i;

              for (i=0; i<200; i++)

                     a[i] = 0;

              a[0] = len = 1;

              for (i=n-m+1; i <= n; i++)

            mul(i);

              for (i=2; i<=m; i++)

                  div(i);

              printf("%d things taken %d at a time is ", n, m);

              for (i=len-1; i>=0; i--)

                     printf("%d", a[i]);

              printf(" exactly.\n");

       }

       return 0;

}

69-2  求余数

问题描述

给定一个基数b和两个非负的基数b整数p和m,计算p mod m并将结果打印为基数b整数。p mod m被定义为最小的非负整数k,对于某个整数a,p=a*m+k。

输入

输入包含多组测试用例。每组测试用例占一行,包含三个无符号整数。第一个数b是介于2和10之间的十进制数。第二个p最多包含1000个介于0和b-1之间的数字。第三个数字m最多包含9个介于0和b-1之间的数字。最后一行为一个0,表示输入的结束。

输出

对于每个测试用例,输出一行,为p mod m的Base-b整数。

输入样例

2 1100 101

10 123456789123456789123456789 1000

0

输出样例

10

789

         (1)编程思路。

        由于被除数p可能有1000位,因此定义字符数组char p[1001]来保存被除数,除数m最多9位,由于输入的是b进制数,为方便转换为10进制整数,也用字符数组char m[10]来保存。输入结束后,将m按权值展开转换为10进制整数d,之后进行大整数p除以整数d的10进制除法运算,求得余数last,余数是一个不超过9位数的10进制整数,最后将其按除b取余的方法转换为b进制整数输出。

       (2)源程序。

#include <stdio.h>

int main()

{

       int b;

       while (scanf("%d", &b) && b)

      {

              char p[1001],m[10];

              scanf("%s %s",p,m);

              int i,d=0;

              for (i=0; m[i]!='\0'; i++)

                     d=d*b+m[i]-'0';

             int last = 0;       // 余数

             for (i=0; p[i]!='\0'; i++)

            {

                     int cur = last*b+p[i]-'0';

                     last = cur%d;

              }

              int len=0,a[10];

             do {

                a[len++]=last%b;

                last/=b;

             } while (last!=0);

            for (i=len-1; i>=0; i--)

                     printf("%d", a[i]);

              printf("\n");

       }

       return 0;

}

69-3  SuperGCD

问题描述

Sheng bill 有着惊人的心算能力,甚至能用大脑计算出两个巨大的数的最大公约数!因此他经常和别人比赛计算最大公约数。有一天Sheng bill很嚣张地找到了你,并要求和你比赛,但是输给 Sheng bill 岂不是很丢脸!所以你决定写一个程序来教训他。

输入

共两行,第一行一个整数a,第二行一个整数 b(0<a,b≤1010000)。

输出

一行,表示a和b的最大公约数。

输入样例

12

54

输出样例

6

         (1)编程思路1。

        利用转辗相除法可以求两个整数的最大公约数。例如求两个整数a和b的最大公约数可以写成如下的循环:

r=a%b;

while(r!=0)

{                

         a = b;

         b = r;

         r=a%b;

}

         循环结束后,整数b的值就是所求的最大公约数。

        由于题目中是求两个巨大的整数的最大公约数,因此需要采用高精度运算。

        将大整数用字符串保存,编写函数void mod(char *a,char *b,char *r)实现r=a%b。

        实现大整数除法的基本的思想是反复做减法,从被除数里最多能减去多少个除数,商就是多少,最后剩下的不够减的部分就是余数。但是不能一个一个地减除数,这样既慢又不好处理。

        实际处理方法应该这样,将除数扩大10倍、100倍、…或10k倍,使得除数和被除数等长,之后再进行减法操作,依次减除数的10k倍、…、除数的100倍、除数的10倍、除数本身。每次记下够减的次数,所得结果就是商,所有相减完成后,被除数的剩余部分就是所求的余数。

       下面以231708除以328为例进行说明。

        先将除数328扩大1000倍,即后面加上3个0,使得除数328000与被除数231708等长,231708减去328000,不够减,因此记下减的次数为0;之后,去掉除数后面的1个0,得到32800(即除数扩大100倍),231708减去32800,够减7次,余下 2108,同样记下减的次数为7;再去掉除数后面的1个0,得到3280(即除数扩大10倍),2108减3280,不够减,同样记下减的次数为0;再去掉除数后面的1个0,此时就是除数本身328,2108减328,够减6次,余下140,同样记下减的次数为6,结束运算。依次记下的减的次数为0706,去掉前导的0,商就是706,余数就是140。

       (2)源程序1。

#include <stdio.h>

#include <string.h>

#define MAXN 10100

void sub(char *a,char *b,char *c)

{

    int len1=strlen(a),len2=strlen(b);

    int x[MAXN],y[MAXN],z[MAXN];

    int len=len1;

    memset(x,0,sizeof(x));

    memset(y,0,sizeof(y));

    memset(z,0,sizeof(z));

    int i,j;

    for (i=len1-1,j=0;i>=0;i--,j++)

        x[j]=a[i]-'0';

    for (i=len2-1,j=0;i>=0;i--,j++)

        y[j]=b[i]-'0';

    int cf=0;

    for (i=0;i<len;i++)

    {

              z[i]=x[i]-y[i]+cf;

              if (z[i]<0)

              {

                     z[i]+=10;

                     cf=-1;

              }

              else

                     cf=0;

       }

    while (len>0 && z[len-1]==0) // 去前置0

        len--;

    if (len==0)  // a-b=0时特判

    {

        c[0]='0';

        c[1]='\0';

        return ;

    }

    for (i=0;i<len;i++)

        c[i]=z[len-1-i]+'0';

    c[len]='\0';

}

int bigger(char *a,char *b)

{

    if (strlen(a)>strlen(b)) return 1;

    if (strlen(a)<strlen(b)) return 0;

    if (strcmp(a,b)>=0) return 1;

    else return 0;

}

void mod(char *a,char *b,char *r)  // r=a%b

{

    if (bigger(a,b)==0)  // 被除数a小于除数b,余数为a

    {

        strcpy(r,a);

        return ;

    }

    char ta[MAXN],tb[MAXN];

    strcpy(ta,a);

    strcpy(tb,b);

    int len1=strlen(ta);

    int len2=strlen(tb);

    int i;

    for (i=len2;i<len1;i++)  // 将b的末尾添加0到与a等长

        tb[i]='0';

    tb[i]='\0';

    for (i=0;i<=len1-len2;i++)

    {

        while (bigger(ta,tb))

        {

            sub(ta,tb,ta);

        }

        if (strlen(tb)>len2) tb[strlen(tb)-1]='\0';

        if (strcmp(ta,"0")==0) break;

    }

    strcpy(r,ta);

}

int main()

{

    char a[MAXN],b[MAXN],r[MAXN];

    scanf("%s%s",a,b);

    mod(a,b,r);

    while (strcmp(r,"0")!=0)

    {

        strcpy(a,b);

        strcpy(b,r);

        mod(a,b,r);

    }

    printf("%s\n",b);

    return 0;

}

        将上面的源程序1提交给洛谷题库https://www.luogu.com.cn/problem/P2152,会超时,显示测评结果如下。

C语言程序设计100例之(69):麦森数

        (3)编程思路2。

        上面源程序1中处理大整数相减时,每个数组元素保存大整数的1位数字,这样既浪费存储空间,又耗费计算时间。因此提交给洛谷题库https://www.luogu.com.cn/problem/P2152,会超时。

        为加快运算速度,可以将输入的两个大整数按每8位数字1组分别保存到两个整型数组a和b中。数组元素a[0]和b[0]分别保存两个整数的组数(每8位1组)。例如,大整数1234567890保存到数组a中为:a[1]=34567890,a[2]=12,a[0]=2。

        按每8位数字一组的处理方式改写源程序1如下。

       (4)源程序2。

#include <stdio.h>

#include <string.h>

#define MAXN 1255

#define BASE 100000000

#define WIDTH 8

void sub(int *a,int *b)  // a=a-b

{

    int i,j,cf=0;

    for (i=a[0],j=b[0];i>=1;i--,j--)

    {

        if (j>=1)

                 a[i]=a[i]-b[j]+cf;

        else

                a[i]=a[i]+cf;

         if (a[i]<0)

        {

                 a[i]+=BASE;

                  cf=-1;

        }

        else

                cf=0;

    }

    int len=a[0];

    i=1;

    while (len>0 && a[i]==0) // 去前置0

    {

        i++;

        len--;

    }

    if (len==0)  // a-b=0时特判

    {

        a[0]=1;

        a[1]=0;

        return ;

    }

    for (j=1;i<=a[0];i++)

        a[j++]=a[i];

    a[0]=len;

}

int bigger(char *a,char *b)

{

    if (strlen(a)>strlen(b)) return 1;

    if (strlen(a)<strlen(b)) return 0;

    if (strcmp(a,b)>=0) return 1;

    else return 0;

}

int cmp(int *x,int *y)   // x>=y ?

{

    if (x[0]>y[0]) return 1;

    else if (x[0]<y[0]) return 0;

    else

    {

        int i;

        for (i=1; i<=x[0]; i++)

            if (x[i]>y[i]) return 1;

            else if (x[i]<y[i]) return 0;

    }

    return 1;

}

void mod(int *a,int *b,int *r)  // r=a%b

{

    int tb[MAXN]={0};

    int i;

    for (i=1;i<=b[0];i++)

        tb[i]=b[i];

    tb[0]=a[0];        // 将tb的末尾添加0到与a等长

    for (i=0;i<=a[0]-b[0];i++)

    {

        while (cmp(a,tb))

        {

            sub(a,tb);

        }

        tb[0]--;

        if (a[1]==0) break;

    }

    for (i=0;i<=a[0];i++)

        r[i]=a[i];

}

void change(char s[],int a[])

{

    int len=strlen(s);

    a[0]=(len+WIDTH-1)/WIDTH;

    int i,k=a[0]+1,p;

    for (i=len-1;i>=0;i--)

    {

       if ((len-1-i)%WIDTH==0) { p=1; k--; }

       a[k]+=p*(s[i]-'0');

       p*=10;

    }

}

void copy(int *a,int *b)

{

    int i;

    memset(a,0,sizeof(int)*MAXN);

    for (i=0;i<=b[0];i++)

        a[i]=b[i];

}

int main()

{

    char s1[8*MAXN],s2[8*MAXN];

    scanf("%s%s",s1,s2);

    int a[MAXN]={0},b[MAXN]={0};

    if (bigger(s1,s2))

    {

        change(s1,a);

        change(s2,b);

    }

    else

    {

        change(s2,a);

        change(s1,b);

    }

    int r[MAXN];

    mod(a,b,r);

    while (r[1]!=0)

    {

        copy(a,b);

        copy(b,r);

        mod(a,b,r);

    }

    printf("%d",b[1]);

    int i;

    for (i=2;i<=b[0];i++)

        printf("%08d",b[i]);

    printf("\n");

    return 0;

}

       再将上面的源程序2提交给洛谷题库https://www.luogu.com.cn/problem/P2152,可以Accepted。

上一篇:openCV python语言入门


下一篇:1234. 倍数问题