洛谷 P1045 & [NOIP2003普及组] 麦森数(快速幂)

题目链接

https://www.luogu.org/problemnew/show/P1045

题目大意

本题目的主要意思就是给定一个p,求2p-1的位数和后500位数。

解题思路

  首先看一下数据范围,我们不难发现此题必须要用高精度来做。但是每一次高精度乘法的复杂度是o(n)的(n为数字的位数),所以很显然需要加一个快速幂。但是事实证明快速幂+高精度也会超时,所以我们必须进一步优化时间。

  根据题意,我们可知,只需要记录下后500位数即可,这里牵扯到一点点数论的知识,这一个数字的后500位是与500位以外的数是没有一点关系的,根据这个性质,我们就可以开一个500大的数组,每一次记录后500位的数字即可。

  但是此题还有一个难点,就是求总的位数,这里要运用数论知识。

  我们不难发现,2n的最后一位不可能是0,所以2p和2p-1的位数是一样的,我们只需求出2p的位数即可。

  我们知道,如果某个数是10n,那么这个数就有n+1位数字。我们知道,log10(2)意思是10的多少次方=2,所以10log10(2)=2。所以我们把2p写成(10log10(2) )p再根据幂的平方运算法则写成10log10(2)*p,而10log10(2)*p的位数是log10(2)*p+1,也就是2p的位数是log10(2)*p+1,也就是2p-1的位数是log10(2)*p+1。

  最后一定要注意输出格式!!本人因此爆零。。

附代码

 1 #include<iostream>                    //快速幂:2的p次方=(2的平方)的p/2次方 = ((2的平方)的平方)的 p/2/2次方...... 
 2 #include<cstdio>                    //       f存的就是底数——2,2的2次方,2的二次方的二次方...... 
 3 #include<cmath>                        //       res 存的就是最后的答案 
 4 #include<cstring>                    //        sav是每一次高精度乘法的临时数组 
 5 using namespace std;
 6 int p,f[510],res[510],sav[510];                //数组开500多一点已足够 
 7 void rr1() {                                //rr1是将答案更新,乘上现在的底数 
 8     memset(sav,0,sizeof(sav));                 
 9     for(int i = 1;i <= 500; i++)            //动手画一下 ,列一个竖式,就会发现[i]*[j]得到的数字其实是[i+j-1]位上的数字 
10         for(int j = 1;j <= 500; j++)        //这里只是乘,还没有进位 
11             if(i+j<=505) sav[i+j-1] += res[i] * f[j];
12     for(int i = 1;i <= 500; i++) {             //进位 
13         sav[i+1] += sav[i] / 10;
14         sav[i] %= 10;
15     }
16     memcpy(res,sav,sizeof(res));            //把求出来的数组sav更新到res中 
17 }
18 void rr2() {                                //rr2求得是底数的平方的值,存在f中 
19     memset(sav,0,sizeof(sav));                //同上 
20     for(int i = 1;i <= 500; i++) 
21         for(int j = 1;j <= 500; j++)
22             if(i+j<=505)sav[i+j-1] += f[i] * f[j];
23     for(int i = 1;i <= 500; i++) { 
24         sav[i+1] += sav[i] / 10;
25         sav[i] %= 10;
26     }
27     memcpy(f,sav,sizeof(f));                
28 }
29 int main() {
30     scanf("%d",&p);
31     printf("%d\n",(int)(log10(2) * p + 1));    //先输出位数 
32     res[1] = 1;
33     f[1] = 2;                                //初始化要为2,否则会一直乘1 
34     while(p != 0) {                            //快速幂 
35         if(p % 2 == 1) rr1();                //任何一个数一直/2最后一定是1,这样就能确保更新答案 
36         p /= 2;                                //rr1,rr2为高精度乘法
37         rr2();                                //每一次都更新一遍f,也就是底数的平方 
38     }
39     res[1] -= 1;
40     for(int i = 500;i >= 1; i--)
41         if(i != 500 && i % 50 == 0) printf("\n%d",res[i]); //注意输出格式。 
42         else printf("%d",res[i]);
43     return 0;
44 }
AC代码

 //NOIP2003普及组t4

posted @ 2019-03-03 01:42  尹昱钦  阅读(729)  评论(3编辑  收藏  举报