洛谷 P1045 麥森數
題目連結: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是將答案更新,乘上現在的底數 8memset(sav,0,sizeof(sav)); 9for(int i = 1;i <= 500; i++)//動手畫一下 ,列一個豎式,就會發現[i]*[j]得到的數字其實是[i+j-1]位上的數字 10for(int j = 1;j <= 500; j++)//這裡只是乘,還沒有進位 11if(i+j<=505) sav[i+j-1] += res[i] * f[j]; 12for(int i = 1;i <= 500; i++) {//進位 13sav[i+1] += sav[i] / 10; 14sav[i] %= 10; 15} 16memcpy(res,sav,sizeof(res));//把求出來的陣列sav更新到res中 17 } 18 void rr2() {//rr2求得是底數的平方的值,存在f中 19memset(sav,0,sizeof(sav));//同上 20for(int i = 1;i <= 500; i++) 21for(int j = 1;j <= 500; j++) 22if(i+j<=505)sav[i+j-1] += f[i] * f[j]; 23for(int i = 1;i <= 500; i++) { 24sav[i+1] += sav[i] / 10; 25sav[i] %= 10; 26} 27memcpy(f,sav,sizeof(f)); 28 } 29 int main() { 30scanf("%d",&p); 31printf("%d\n",(int)(log10(2) * p + 1));//先輸出位數 32res[1] = 1; 33f[1] = 2;//初始化要為2,否則會一直乘1 34while(p != 0) {//快速冪 35if(p % 2 == 1) rr1();//任何一個數一直/2最後一定是1,這樣就能確保更新答案 36p /= 2;//rr1,rr2為高精度乘法 37rr2();//每一次都更新一遍f,也就是底數的平方 38} 39res[1] -= 1; 40for(int i = 500;i >= 1; i--) 41if(i != 500 && i % 50 == 0) printf("\n%d",res[i]); //注意輸出格式。 42else printf("%d",res[i]); 43return 0; 44 } AC程式碼