北大ACM1001

求高精度冪
Time Limit: 500MS   Memory Limit: 10000K
Total Submissions: 87719   Accepted: 20833

Description

對數值很大、精度很高的數進行高精度計算是一類十分常見的問題。比如,對國債進行計算就是屬於這類問題。 

現在要你解決的問題是:對一個實數R( 0.0 < R < 99.999),要求寫程序精確計算 R 的 n 次方(Rn),其中n 是整數並且 0 < n<= 25。

Input

T輸入包括多組 R 和 n。 R 的值佔第 1 到第 6 列,n 的值佔第 8 和第 9列。

Output

對於每組輸入,要求輸出一行,該行包含精確的 R 的 n 次方。輸出需要去掉前導的 0 後不要的 0。如果輸出是整數,不要輸出小數點。

Sample Input

95.123 12
0.4321 20
5.1234 15
6.7592  9
98.999 10
1.0100 12

Sample Output

548815620517731830194541.899025343415715973535967221869852721
.00000005148554641076956121994511276767154838481760200726351203835429763013462401
43992025569.928573701266488041146654993318703707511666295476720493953024
29448126.764121021618164430206909037173276672
90429072743629540498.107596019456651774561044010001
1.126825030131969720661201

分析:
在計算機上進行高精度計算,首先要處理好以下幾個基本問題:
 1、數據的接收與存儲;
 2、計算結果位數的確定;
 3、進位處理和借位處理;
 4、商和餘數的求法; 


輸入和存儲
運算因子超出了整型、實型能表示的範圍,肯定不能直接用一個數的形式來表示。能表示多個數的數據類型有兩種:數組和字符串。
(1)數組:每個數組元素存儲1位(在優化時,這裏是一個重點!),有多少位就需要多少個數組元素;優點:每一位都是數的形式,可以直接加減;運算時非常方便缺點:數組不能直接輸入;輸入時每兩位數之間必須有分隔符,不符合數值的輸入習慣;
(2)字符串:字符串的最大長度是255,可以表示255位。優點:能直接輸入輸出,輸入時,每兩位數之間不必分隔符,符合數值的輸入習慣;缺點:字符串中的每一位是一個字符,不能直接進行運算,必須先將它轉化爲數值再進行運算;運算時非常不方便(注意‘0’的問題)。




優化:
一個數組元素存放四位數
     注意:是加減法時可以用interger,但是當是乘法的時候,就要用int64,否則會出現越界的情況。
     還有就是:輸出時對非最高位的補零處理。


另一個問題:
存儲順序
  正序??
  逆序??


還有就是一定不要忘了初始化!!
計算結果位數的確定
兩數之和的位數最大爲較大的數的位數加1。
乘積的位數最大爲兩個因子的位數之和。
階乘:lgn!=lgn+lg(n-1)+lg(n-2)...................+lg3+lg2+lg1
=lnn/ln10+ln(n-1)/ln10+ln(n-2)/ln10+................+ln3/ln10+ ln2/ln10+ln1/ln10
=trunc(1/ln10*(lnn+ln(n-1)+ln(n-2)+...........+ln3+ln2+ln1)
乘方:lg(a?^b)=trunc(lg(a^b))+1
             =trunc(b*lga)+1
             =trunc(b*lna/ln10)+1


高精度的加法
ncarry=0;
      for (i=0;i<=len;i++)
      {
            k=a[i]+b[i]+ncarry;
            a[i+1]+=k/N;
            ncarry=k%N;
     }
     當最後ncarry>0時,len會變化!!
高精度的減法


  先比較大小,大的爲a,用一個變量記錄符號。
  ncarry=0;
  for (i=0;i<=len;i++)
  {
       if (a[i]-b[i]-ncarry>=0)
             a[i]=a[i]-b[i]-ncarry,ncarry=0;
      else
            a[i]=a[i]+N-b[i]-ncarry,ncarry=1;
  }
高精度的乘法
For (i=0;i<=lena;i++)
    for (j=0;j<=lenb;j++)
        c[i+j]+=a[i]*b[j];
For (i=0;i<=lena+lenb;i++)
     {
          c[i+j+1]+=c[i+j]/N;
          c[i+j]=c[i+j]/N;
     }
高精度的除法
A÷B精確值有兩種情況:①、A能被B整除,沒有餘數。②、A不能被B整除,對餘數進行處理。首先,我們知道,在做除法運算時,有一個不變的量和三個變化的量,不變的量是除數,三個變化的量分別是:被除數、商和餘數。
可以用減法代替除法運算:不斷比較A[1..n]與B[1..n]的大小,如果A[1..n]>=B[1..n]則商C[1..n]+1→C[1..n],然後就是一個減法過程:A[1..n]-B[1..n]→A[1..n]。
由於簡單的減法速度太慢,故必須進行優化。
       設置一個位置值J,當A[1..n]>B[1..n]時,B[1..n]左移→B[0..n],j:=j+1,即令B[1..n]增大10倍。這樣就減少了減法的次數。當j>0且A[1..n]<B[1..n]時,B[0..n]右移




求n!






一個例子:計算5*6*7*8
方法一:順序連乘
5*6=30,1*1=1次乘法
30*7=210,2*1=2次乘法
210*8=1680,3*1=3次乘法
方法二:非順序連乘
5*6=30,1*1=1次乘法
7*8=56,1*1= 1次乘法
30*56=1680,2*2=4次乘法 


若“n位數*m位數=n+m位數”,則n個單精度數。
無論以何種順序相乘,乘法次數一定爲n(n-1)/2次。(n爲積的位數)
證明:
設F(n)表示乘法次數,則F(1)=0,滿足題設
設k<n時,F(k)=k(k-1)/2,現在計算F(n)
設最後一次乘法計算爲“k位數*(n-k)位數”,則
F(n)=F(k)+F(n-k)+k (n-k)=n(n-1)/2(與k的選擇無關)


考慮k+t個單精度數相乘
     a1*a2*…*ak *ak+1*…*ak+t
設a1*a2*…*ak結果爲m位高進制數(假設已經算出)
ak+1*…*ak+t結果爲1位高進制數
若順序相乘,需要t次“m位數*1位數” ,共mt次乘法
可以先計算ak+1*…*ak+t,再一起乘,只需要m+t次乘法
        在設置了緩存的前提下,計算m個單精度數的積,如果結果爲n位數,則乘法次數約爲n(n–1)/2次,與m關係不大
設S=a1*a2*…*am,S是n位高進制數
可以把乘法的過程近似看做,先將這m個數分爲n組,每組的積仍然是一個單精度數,最後計算後面這n個數的積。時間主要集中在求最後n個數的積上,這時基本上滿足“n位數*m位數=n+m位數”,故乘法次數可近似的看做n(n-1)/2次
10!=28*34*52*7
n!分解質因數的複雜度遠小於nlogn,可以忽略不計
與普通算法相比,分解質因數後,雖然因子個數m變多了,但結果的位數n沒有變,只要使用了緩存,乘法次數還是約爲n(n-1)/2次
因此,分解質因數不會變慢(這也可以通過實踐來說明)
分解質因數之後,出現了大量求乘冪的運算,我們可以優化求乘冪的算法。這樣,分解質因數的好處就體現出來




二分法求乘冪


a2n+1=a2n*a
a2n=(an)2
其中,a是單精度數
二分法求乘冪之優化平方算法
怎樣優化
(a+b)2=a2+2ab+b2
例:123452=1232*10000+452+2*123*45*100
把一個n位數分爲一個t位數和一個n-t位數,再求平方
怎樣分
設求n位數的平方需要F(n)次乘法
F(n)=F(t)+F(n-t)+t(n-t),F(1)=1
用數學歸納法,可證明F(n)恆等於n(n+1)/2 
所以,無論怎樣分,效率都是一樣
將n位數分爲一個1位數和n–1位數,這樣處理比較方便
優化:


計算S=ax+kbx=(ab)xak
當k<xlogab時,採用(ab)xak比較好,否則採用ax+kbx更快
可以先計算兩種算法的乘法次數,再解不等式,就可以得到結論
也可以換一個角度來分析。其實,兩種算法主要差別在最後一步求積上。由於兩種方法,積的位數都是一樣的,所以兩個因數的差越大,乘法次數就越小
∴當axbx–ak>ax+k–bx時,選用(ab)xak,反之,則採用ax+kbx。
∴axbx–ak>ax+k–bx
∴(bx–ak)(ax+1)>0
∴bx>ak
這時k<xlogab

這道題:
1.處理小數點問題,以及反序。
2.進行n次乘法。
3.進行輸出,並加上小數點。


代碼:
#include <stdio.h>

#include <iostream>

#include <string>


using namespace std;


int main()

{

  string mlp;     //乘數


    int power;     //乘數的冪

      int r[151];    //保存結果

      int hdot;


 while(cin>>mlp>>power)

   {

            hdot=0;

              for(int t=0;t<150;t++)

                {

                    r[t]=-1;

             }

            if(mlp.find(".")!=string::npos)

        hdot=mlp.length()-mlp.find(".")-1;

         string::iterator itr=mlp.end()-1;


         while(hdot>0&&itr>=mlp.begin())

                {

                    if(*itr!='0')

                        {break;}

                     hdot--;

                      itr--;

               }


         int cn=0;


         while(itr>=mlp.begin())

               {

                    if(*itr!='.')

                        {

                            r[cn]=*itr-'0';

                              cn++;

                        }

                    itr--;

               }


         int k=cn-1;

          int m=0;     //保存臨時數;


           while(k>-1)

           {

                    m=m*10+r[k];

                 k--;

         }


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

              {

                    int j=0;

                     while(r[j]>-1)

                        {

                            r[j]=r[j]*m;

                         j++;

                 }

                    j=0;

                 while(r[j]>-1)

                        {

                            if(r[j+1]==-1&&r[j]>=10)

                                      r[j+1]=r[j]/10;

                              else

                                 r[j+1]+=r[j]/10;

                             r[j]=r[j];

                           j++;

                 }

            }


         hdot=hdot*power;

             int cnt=0;


                while(r[cnt]>-1)

              {

                    cnt++;

               }

            if(hdot>=cnt)

         {

                    cout<<".";

                     while(hdot>cnt)

                       {

                            cout<<"0";

                             hdot--;

                      }

                    hdot=0;

              }

            for(k=cnt-1;k>=0;k--)

         {

                    if((k+1)==hdot&&hdot!=0)

                             cout<<".";

                     cout<<r[k];

            }

            cout<<endl;

    }

    return 0;

}


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