求高精度冪
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。
現在要你解決的問題是:對一個實數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;
}