【轉】[NOIP2003普及組]麥森數


來源:http://vivid.name/tech/mason.html

 

不得不紀念一下這道題,因為我今天一整天的時間都花到這道題上了。因為這道題,我學會了快速冪,學會了高精度乘高精度,學會了靜態查錯,學會了一個小小的變量的使用可能會導致整個程序掛掉。。

 

Description

形如2^P-1的素數稱為麥森數,這時P一定也是個素數。但反過來不一定,即如果P是個素數,2^P-1不一定也是素數。到1998年底,人們已找到了37個麥森數。最大的一個是P=3021377,它有909526位。麥森數有許多重要應用,它與完全數密切相關。
任務:從文件中輸入P(1000 < P < 3100000),計算2^P-1的位數和最后500位數字(用十進制高精度數表示)

Input

只包含一個整數P(1000 < P < 3100000)

Output

第一行:十進制高精度數2^P-1的位數。
第2-11行:十進制高精度數2^P-1的最后500位數字。(每行輸出50位,共輸出10行,不足500位時高位補0)
不必驗證2^P-1與P是否為素數。

Sample Input

1279

Sample Output

386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
38615262247266704805319112350403608059673360298012
23944173232418484242161395428100779138356624832346
49081399066056773207629241295093892203457731833496
61583550472959420547689811211693677147548478866962
50138443826029173234888531116082853841658502825560
46662248318909188018470682222031405210266984354887
32958028878050869736186900714720710555703168729087

看到這題第一感覺是簡單,很容易就看懂了。然后,就沒有然后了。不知道怎么做了,直接不斷乘2肯定不僅超時而且超出范圍。。明顯用高精度。然后不知道用高精度乘這么多次會不會超時,於是想到快速冪。

想歸想,這倆算法我都不會。於是問百度,看題解,看了好久,終於在上午似懂非懂地編出了快速冪(求a^b%m)。下午繼續研究,終於又編出了高精度乘高精度的程序。這可完全是我自己蒙出來的,我沒找到高精度乘高精度的例程。所以毫無懸念地在2^p,p上萬時掛掉了。
就是因為自己想的高*高用了t、tt以及計算時處理進位,程序在次數較高的時候才掛掉了。這三個全部改掉才可以,只改掉任何一個仍然會掛。

本題分兩問,第一問求位數,可以證明:當x有n位時,必有10^(n-1)<=x<10^n(如x有3位時必有100=10^2<=x<1000=10^3),取常用對數,n-1<=lgx<n,即lgx的整數部分是n-1,也就是說數x的位數是lg(x)的整數部分+1。故欲求x的位數只需求floor(log10(x)+1).

  


PS:注意:原文這里描述是“故欲求x的位數只需求floor(log10(x)+1+0.5)(floor(x+0.5)是計算x的整數部分,這樣可以避免浮點誤差)。

其實這個浮點數x加上0.5的最主要功能是四舍五入。這里多加0.5的話,會使得有些情況下的計算結果比正確結果多1. 


 

然后第二問是去掉了取模運算、用上高精度的快速冪:

while(p>0)
{
if(p==1)
{
    mul(ans,a);
    break;
  }
else
{ if(p%2) mul(ans,a); p/=2; mul(a,a);
} }

話說我看了很多題解,都發現里面有一句“末位不要忘了減1”,一直百思不得其解。直到最后我才明白,原來是因為題目讓算2^p-1。。還有,寫這個程序的時候除了很多小錯,比如遞減的for循環寫成了i++,x[i]*y[j]寫成了x[i]*y[i]……這些都是編譯器查不出來的,只有靜態查錯,也就是傳說中的用眼睛自己看,才可以查到。從此以后我再也不敢完全依靠編譯器了。

廢話不多說,直接貼AC程序:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<string.h>
 4 #include<math.h>
 5 
 6 void mul(int x[],int y[])
 7 {
 8     /*   x*y->x   */
 9     int tmp[520]={0},lx=500,ly=500,i,j,len;
10     memset(tmp,0,sizeof(tmp));
11     while(x[lx]==0&&lx>0) lx--; //計算x首位位置
12     while(y[ly]==0&&ly>0) ly--; //計算y首位位置
13     len=lx+ly;
14     for(i=1;i<=ly;i++)
15        for(j=1;j<=lx;j++)
16            if(i+j-1<=500) tmp[i+j-1]+=y[i]*x[j];
17     for(i=1;i<=500;i++) {tmp[i+1]+=tmp[i]/10; tmp[i]%=10; if(i<500&&tmp[i+1]==0) {len=i; break;}}
18     for(i=500;i>0;i--) x[i]=tmp[i];
19 }
20 int main()
21 {
22     long p;
23     int ans[501]={0},a[501]={0},i;
24     scanf("%ld",&p);
25     printf("%ld\n",(long)floor(p*log10(2)+1));
26     /*快速冪求2^p,同時用高精度乘法*/
27     ans[1]=1; a[1]=2;
28     while(p>0){
29     if(p==1) {mul(ans,a); break;}
30     else{
31     if(p%2) mul(ans,a);
32     p/=2;
33     mul(a,a);}
34     }
35     ans[1]-=1;
36     for(i=500;i>0;i--) {printf("%d",ans[i]); if((i-1)%50==0) printf("\n");}
37     //system("pause");
38     return 0;
39 }
View Code

 

 

參考原文的代碼,簡化了一些地方:

 1 #include<stdio.h>
 2 #include<math.h>
 3 //#include<string.h>
 4 void mul(int x[],int y[]);
 5 int main()
 6 {
 7     long p;
 8     int num=0;
 9     int ans[501]={0},a[501]={0},i;
10     freopen("Mason.in","r",stdin);
11     freopen("Mason.out","w",stdout);
12     scanf("%ld",&p);
13     num=(int)floor(p*log10(2)+1);
14     printf("%d\n",num);
15     
16     /*快速冪求2^p,同時用高精度乘法*/
17     ans[1]=1; a[1]=2;
18     while(p>0)
19     {
20         if(p&1)      //if(p%2==1)
21             mul(ans,a);
22         p=p>>1;      //p=p/2;
23         mul(a,a);    //a*a -> a
24     }
25     ans[1]-=1;     //這個地方其實直接減1會有bug。當ans[1]為0的時候是錯誤的結果。所以可以采用下面的方法減1。但是對這個題目而言,2^p-1必然是奇數,也即個位是不可能為0。(ans[1]不會為0.) 
26     /*for(i=1;i<=500;i++)
27     {
28         if(ans[i]>0) {ans[i]--;break;}
29     }
30     for(;i>=1;i--) ans[i]=9;*/
31     for(i=500;i>0;i--) {printf("%d",ans[i]); if((i-1)%50==0) printf("\n");}
32     printf("\n");
33     return 0;
34 }
35 void mul(int x[],int y[])
36 {
37     /*   x*y->x   */
38     int tmp[520]={0},lx=500,ly=500,i,j,len;  //tem[]一定要清零。
39     //memset(tmp,0,sizeof(tmp));
40     while(x[lx]==0&&lx>0) lx--; //計算x首位位置
41     while(y[ly]==0&&ly>0) ly--; //計算y首位位置
42     len=lx+ly;
43     for(i=1;i<=ly;i++)
44        for(j=1;j<=lx;j++)
45            if(i+j-1<=500) tmp[i+j-1]+=y[i]*x[j];   //if語句是保證只保留500位 
46     for(i=1;i<=500;i++) 
47     {
48         tmp[i+1]+=tmp[i]/10; //把進位的值加到高位 
49         tmp[i]%=10;
50         /*if(i<500&&tmp[i+1]==0)
51         {len=i; break;}*/  //這個地方沒理解其用意,似乎只是優化循環次數。但len沒有使用的機會,所以干脆刪除掉比較好。
52     }  
53     for(i=500;i>0;i--) x[i]=tmp[i];  //把結果復制到x數組 
54 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM