求n!的最高位數字

CSDN上某網友,提出以下問題:

Time Limit:1000MSMemory Limit:30000KB


Description

求n的階乘的最高位數。

例如:5! = 120,所以最高位爲1, 10!=3628800,所以最高位爲3

 

Input

每個數據包含一行,每行有一個整數N(0<=N<=10000000)

 

Output

對於每個測試數據,輸出n!的最高位數字

 

Sample Input

5

10

 

Sample Output

1

3

 

很顯然,不能先計算出階乘,然後再去階乘的最高位數字,因爲當n(=13)很小的時候,n!在32-bit的機器上就會溢出,即使在64-bit的機器上,要溢出也是很容易的事情(當n = 21時)。

 

第一種方法:

很自然地,想到Sterling公式:

, 也就是說當n比較大的時候有:,且n越大,結果越精確,兩邊同時取常用對數:

 ,則其整數部分加1,爲n!的位數,比如,其整數部分爲2,加1則等於3,這就說說明123是一個3位數。如果我們把整數部分去掉,那麼就相當於125除以100,因爲,因此可以得知:

,也就是說,對1.25向下取整,就可以很容易得到125這個數字的最高位數字1。

 

對於求n!的最高位數字,我們可以用同樣的方法:

n!的最高位 = ,其中[]表示向下取整。

 

由於Sterling公式在n較小的時候存在一定的誤差,所以,在寫代碼時候,對於較小的n的階乘的最高位數字,可以採用直接輸出的方式,具體代碼如下:

#include<iostream>

#include<math.h>

using namespacestd;

 

const double PI =3.14159265358979;

const double E =2.71828459045;

int main(int argc,char *argv[])

{

    int n = 0;

    int firstnumber = 0;

    double log_n_factorial = 0.0;

    do

    {

        cin >> n;

   

        log_n_factorial = 0.5 * log(2 * PI *(double)n) / log(10.0) + (double)n * log((double)n / E) / log(10.0);

        log_n_factorial -=(int)log_n_factorial;

 

        firstnumber = exp(log_n_factorial *log(10.0));

 

        // 1. 用sterling公式計算較小數字的階乘的時候,存在比較大的誤差,隨着數字的增大誤差越來越小。

        // 2. 用switch...case...處理幾個比較小的數字,其他的(見default)就均可以使用Sterling公式計算出來的結果了。

        switch(n)

        {

        case 0:

            cout << "1"<< endl;

            break;

        case 1:

            cout << "1"<< endl;

            break;

        case 2:

            cout << "2"<< endl;

            break;

        case 3:

            cout << "6"<< endl;

            break;

        case 7:

            cout << "5"<< endl;

            break;

        case 8:

            cout << "4"<< endl;

            break;

        default:

            cout << firstnumber <<endl;

        }

    }while(n != 0);    // n = 0時退出循環

 

    return 0;

}

 

第二種方法:

不使用Sterling公式,也可以求解。我們知道:

,兩邊也取常用對數:


其他的做法就和第一種方案類似了。很顯然,用這種方法求得的n!的對數是精確值,而不是用Sterling公式算出來的近似值,因此不需要像第一種方法那樣在計算較小的n的階乘時,直接返回結果。

 

代碼如下:

#include<iostream>

#include<math.h>

using namespacestd;

 

int main(int argc,char *argv[])

{

    int n = 0, firstnumber = 0;

    double log_n_factorial = 0.0;

         do

         {

                   cin >> n;

   

                   for(int i = 1; i <= n;++i)

                   {

                            log_n_factorial +=log((double)i) / log(10.0);

                   }

 

                   log_n_factorial -=(int)log_n_factorial;  

 

                   firstnumber =exp(log_n_factorial * log(10.0));

 

                   cout << firstnumber<< endl;

 

                   log_n_factorial = 0.0;

 

         }while(n != 0);    // n = 0時退出循環

 

    return 0;

}

 

結論分析:

1.       第二種方法的計算性能儘管也很高,但第一種的計算性能要比第二種高很多,數字越大的時候越是如此。第一種算法的複雜度是一個常量,第二種算法的複雜度是線性的;

2.       第一種算法的結果是近似值,第二種算法的結果是相對精確值(僅受限於計算機的精度)

3.       由於C語言中沒有求常用對數的函數,因此用到了換底公式:

4.      推而廣之,如果要計算n!的前兩位數字,那麼僅需要將

          log_n_factorial -= (int)log_n_factorial;這行代碼改爲:

          log_n_factorial -= ((int)log_n_factorial - 1); 即可,當然首先要確定n!階乘的位數至少要有兩項。

          如果要計算n!的前三、四…位數字,所用方法與上面描述的方法類似。

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章