高精度算法

高精度

1.什麼是高精度

高精度算法,屬於處理大數字的數學計算方法。在一般的科學計算中,會經常算到小數點後幾百位或者更多,當然也可能是幾千億幾百億的大數字。一般這類數字我們統稱爲高精度數,高精度算法是用計算機對於超大數據的一種模擬加,減,乘,除,乘方,階乘,開方等運算。對於非常龐大的數字無法在計算機中正常存儲,於是,我們可以將這個數字拆開,拆成一位一位的,或者是幾位幾位的存儲到一個數組中, 用一個數組去表示一個數字,這樣這個數字就被稱爲是高精度數。高精度算法就是能處理高精度數各種運算的算法,但又因其特殊性,故從普通數的算法中分離,自成一家。

對於這類問題,不要指望long double這些東西了,基本數據類型不可能存的下。我們可以把這兩個數當成字符串輸入到數組中,然後模擬手動的豎式運算(不會的話,回去上小學)得出結果。

說白了,高精度計算就是解決long long也解決不了的問題。

2.高精度的作用

正如上面所說的,高精度的作用就是對於一些異常之大的數字進行加減乘除乘方階乘開方等運算。比如給你一道a+b的題目,讀入a和b,讓你輸出它們的和,但a和b的範圍都是小於等於10的6666次方,這個時候你就只能用高精度了。

3.高精度讀入處理數據

當一個數據很大的時候,我們用一個整數類型是存不下的,所以我們可以先用一個字符串輸入,這樣就可以輸入很長的數,然後再利用字符串函數和操作運算,將每一位數取出,存入一個數組裏,我們用數組裏的每一位表示這個數的每一個數位。

例如:998244353用數組儲存下來,a{3,5,3,4,4,2,8,9,9},一般是倒着存(從低位到高位,因爲整數沒有除個位以下的數位,但你的最高位還可以進位,那麼你就又要開一個位置來存這個新的最高位)。

高精度讀入Code

char s[6666];
int a[6666];
 
int main(){
    scanf("%s",s+1);//用字符串讀入
    len=strlen(s+1);//這個數的長度爲len
    for(int i=1;i<=len;i++){
        a[i]=s[len-i+1]-'0';//倒敘儲存,每一位存一個數
    }
    return 0;
}

高精度輸出Code


int a[6666]
 
void write(int a[]){
    for(int i=lena;i>0;i--){
        printf("%d",a[i]);//一位一位輸出這個數
    }
}

4.高精度比較大小

處理完數據之後,假設我們要比較兩個數那個大怎麼辦呢?

我們先模擬比較1314520和1314530,首先我們看兩個數的長度(即len1和len2)如果哪個數的len更長一點,那麼這個數肯定要比另一個數大,否則我們才繼續比較下去,這裏兩個數的長度是一樣的,所以接下來我們就看這兩個數的最高位(即1和1),相等,則繼續比較下一位(3和3),也一樣,繼續比較下一位…直到比到十位的時候(2和3),因爲2<3,所以第一個數<第二個數,直接退出。

所以,高精度比較大小的步驟大致如下:

1、比較兩個數的長度,長度更長的數越大。

2、如果兩個數長度相等,那麼就從高位到低位一位一位比較,如果某一位數字不同時,較大的數大。否則繼續比較下一位。

3、如果比到最後都沒有比出誰大誰小,就說明這兩個數一樣大。

高精度比較大小Code

//比較a和b的大小,如果a>b則返回真,否則返回假
int a[6666],b[6666];
 
int compare(){
    if(lena>lenb) return 1;//lena表示a這個數的長度,lenb則表示b的長度
    if(lenb>lena) return 0;//步驟1
    for(int i=lena;i>0;i--){//從高位到底位一位一位比較
        if(a[i]>b[i]) return 1;
        if(b[i]>a[i]) return 0;
    }//步驟2
    return 0;//步驟3,a=b,即a不大於b
}

5.高精度處理進位與借位

一、那麼我們怎麼處理進位呢?

其實也很簡單,我們再來模擬一下(模擬大法好,不會也得會),1439+887的時候,首先我們最低位相加,得到16,那麼答案最低位就是6,再進個1,然後兩數的十位相加,3+8=11,然後再加上進位的1,就是11+1=12,所以答案十位就是2,再進1,4+8+1=13,答案百位是3,進1,1+0+1=2,答案千位是2。所以結果就是6232!哦,不對反了,是2326,呵呵,這裏要注意一下,輸出的時候是倒着輸出的,千萬不要忘了。

總結一下,進位的步驟大致如下:

1、將當前位置的數相加,當前位的結果就是當前結果除以10的餘數。

2、再講當前結果除以10加到高位,表示進位。

注意:有同學可能會有疑問,爲什麼一定要倒着儲存這個數呢?順着存不是更好嗎?這裏我舉一個非常簡單的例子,比如10+90,誰都知道答案是100,那麼我們來看看順着儲存和倒着儲存有什麼區別:

1.順着存:a={1,0},b={9,0},當a的最高位(即數組第1位)加上b的最高位(即數組第2位)時我們是不是得進位?!?但是a的最高位是數組的第一位,那麼我們要進位到a數組的第幾個位置呢?第0個位置?太複雜了。還是存在第一個位置並把所有的數往數組右邊移動一位?那更不行,時間複雜度又太高,所以不好辦。

2.倒着存:a={0,1},b={0,9},當a的最高位(即數組第2位)加上b的最高位(即數組第2位)時我們同樣要進位,這個時候就好辦了,直接進位到數組的第三位就好了,所以答案的數組就是{0,0,1},倒過來輸出答案100。

高精度進位Code

int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
 
int main(){
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]+b[i];
        c[i+1]=c[i]/10;
        c[i]=c[i]%10;
    }
    while(c[lenc+1]>0) lenc++;//答案的長度有時還會增加
}

二、接下來講講當減法的時候如何借位

1、將當前位置的數向減。

2、如果結果大於或等於0就直接作爲當前位的答案。

3、否則將結果加10作爲當前位的答案,在將高位的數-1即可。

高精度借位Code

int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
 
int main(){
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]-b[i];
        if(c[i]<0) c[i]=c[i]+10,c[i+1]=-1;
    }
    while(c[lenc]==0&&lenc>1) lenc--;//細節,如果a-b結果爲0,那麼也要輸出一個0
}

6.高精度加法
至此,就可以進行任意你想進行的運算了,首先我們來看看加法,其實上面的代碼已經差不多寫出來了。

高精度加法Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]+b[i];
        c[i+1]=c[i]/10;
        c[i]=c[i]%10;
    }
    while(c[lenc+1]>0) lenc++;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

7.高精度減法
高精度減法Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    if(lenb>lena||(lena==lenb&&s2>s1)){//如果第二個數比第一個數大,那麼結果是負數
    	printf("-");
    	swap(s1,s2);//swap是C++自帶函數可以直接調用
    	swap(lena,lenb);//別忘了交換長度
    }
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=max(lena,lenb);
    for(int i=1;i<=lenc;i++){
        c[i]=c[i]+a[i]-b[i];
        if(c[i]<0) c[i]=c[i]+10,c[i+1]=-1;
    }
    while(c[lenc]==0&&lenc>1) lenc--;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

8.高精度乘法
1.高精度乘以單精度
高精度乘以單精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b;
int lena;
char s1[6666];
 
int main(){
    scanf("%s %d",s1+1,&b);
    lena=strlen(s1+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lena;i++) a[i]*=b;
    for(int i=1;i<=lena;i++){
    	a[i+1]+=a[i]/10;
    	a[i]%=10;
    }
    while(a[lena+1]>0) lena++;
    for(int i=lena;i>0;i--) printf("%d",a[i]);
}

2.高精度乘以高精度
高精度乘以高精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],c[6666];
int lena,lenb,lenc;
char s1[6666],s2[6666];
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    lena=strlen(s1+1);
    lenb=strlen(s2+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=1;i<=lenb;i++) b[i]=s2[lenb-i+1]-'0';
    lenc=lena+lenb-1;
    for(int i=1;i<=lena;i++){
    	for(int j=1;j<=lenb;j++){
    		c[i+j-1]+=a[i]*b[j];
    		c[i+j]+=c[i+j-1]/10;
    		c[i+j-1]%=10;
		}
	}
    while(c[lenc+1]>0) lenc++;
    for(int i=lenc;i>0;i--) printf("%d",c[i]);
}

9.高精度除法

1.高精度除以單精度
手動模擬一下,我們只要記錄一個r,表示當前的餘數,然後不斷除就可以了。

高精度除以單精度Code


#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b,r;
int lena;
char s1[6666];
 
int main(){
    scanf("%s %d",s1+1,&b);
    lena=strlen(s1+1);
    for(int i=1;i<=lena;i++) a[i]=s1[lena-i+1]-'0';
    for(int i=lena;i>0;i--){
    	r=r*10+a[i];
    	a[i]=r/b;
    	r=r%b;
    }
    while(a[lena]==0&&lena>1) lena--;
    for(int i=lena;i>0;i--) printf("%d",a[i]);
}

2.高精度除以高精度
很多人都不知道高精度如何整除以一個高精度,那麼這裏我就講一下我的方法吧,可能不是最優的。

我們知道除法是乘法的逆運算,那麼我們爲什麼不可以看做是我們現在要求一個數乘以除數等於被除數呢?那麼枚舉肯定是不行的,這個時候我們就要用到二分啦(二分大發好啊~),沒錯高精度二分商,實際上就是一個高精度加法(mid=l+r),然後高精度除以單精度(mid/2),最後再高精度減法(r=mid-1)就可以實現二分了,我們二分出來的數直接高精度乘以高精度判斷一下就可以了(代碼中數組中的第0個位置表示此數的長度,瞬間暴露PC黨…)。

高精度除以高精度Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666],s2[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    memset(t,0,sizeof(t));
    t[0]=b[0]+mid[0]-1;
    for(int i=1;i<=b[0];i++){
        for(int j=1;j<=mid[0];j++){
            t[i+j-1]+=b[i]*mid[j];
            t[i+j]+=t[i+j-1]/10;
            t[i+j-1]%=10;
        }
    }
    while(t[t[0]+1]>0) t[0]++;
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;
            r[i]+=10;
        }
    }
}
 
int main(){
    scanf("%s %s",s1+1,s2+1);
    a[0]=r[0]=strlen(s1+1);
    b[0]=strlen(s2+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    for(int i=1;i<=b[0];i++) b[i]=s2[b[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

碼打的有點醜,不要介意…

10.高精度開平方
高精度開平方其實也還算比較簡單,有一次考試就有一道原題,我一怒之下把它給切了。

沒錯,還是二分。二分答案。利用高精度加法和高精度除以單精度可以實現二分的效果,然後直接高精度乘法乘起來再高精度比較一下大小,再用高精度減法移動一下l和r就可以了。其實這也算是高精度比較綜合的做法了。碼量雖然驚人,但其實高精度除以高精度改一改就好了

高精度開平方Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666],s2[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    memset(t,0,sizeof(t));
    t[0]=mid[0]+mid[0]-1;
    for(int i=1;i<=mid[0];i++){
        for(int j=1;j<=mid[0];j++){
            t[i+j-1]+=mid[i]*mid[j];
            t[i+j]+=t[i+j-1]/10;
            t[i+j-1]%=10;
        }
    }
    while(t[t[0]+1]>0) t[0]++;
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;
            r[i]+=10;
        }
    }
}
 
int main(){
    scanf("%s",s1+1);
    a[0]=r[0]=strlen(s1+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

11.高精度開n次方
稍加處理即可:開平方時將二分的答案乘兩次,那麼開n次方不就是將二分出來的答案乘n次嗎?

高精度開n次方Code

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
using namespace std;
int n,a[6666],b[6666],ans[6666],t[6666],l[6666],r[6666],mid[6666];
char s1[6666];
 
int compare(int a[],int b[]){
    if(a[0]>b[0]) return 0;
    if(a[0]<b[0]) return 1;
    for(int i=a[0];i>0;i--){
        if(a[i]>b[i]) return 0;
        if(a[i]<b[i]) return 1;
    }
    return 1;
}
 
void add(){
    mid[0]=max(l[0],r[0]);
    for(int i=1;i<=mid[0];i++) mid[i]=l[i]+r[i];
    while(mid[mid[0]+1]>0) mid[0]++;
    for(int i=1;i<=mid[0];i++){
        mid[i+1]+=mid[i]/10;
        mid[i]%=10;
    }
    while(mid[mid[0]+1]>0) mid[0]++;
}
 
void times(){
    for(int i=0;i<=mid[0];i++) b[i]=mid[i];
    for(int k=1;k<=n-1;k++){
        memset(t,0,sizeof(t));
        t[0]=b[0]+mid[0]-1;
        for(int i=1;i<=b[0];i++){
            for(int j=1;j<=mid[0];j++){
                t[i+j-1]+=b[i]*mid[j];
                t[i+j]+=t[i+j-1]/10;
                t[i+j-1]%=10;
            }
        }
        while(t[t[0]+1]>0) t[0]++;
        for(int i=0;i<=t[0];i++) b[i]=t[i];
    }
}
 
void div(){
    int r=0;
    for(int i=mid[0];i>0;i--){
        r=r*10+mid[i];
        mid[i]=r/2;
        r%=2;
    }
    while(mid[mid[0]]==0) mid[0]--;
}
 
void left(){
    for(int i=0;i<=mid[0];i++) l[i]=ans[i]=mid[i];
    l[1]++;
    for(int i=1;i<=l[0];i++){
        l[i+1]+=l[i]/10;
        l[i]%=10;
    }
}
 
void right(){
    for(int i=0;i<=mid[0];i++) r[i]=mid[i];
    r[1]--;
    for(int i=1;i<=r[0];i++){
        if(r[i]<0){
            r[i+1]--;			
            r[i]+=10;
        }
    }
}
 
int main(){
    scanf("%d\n",&n);
    scanf("%s",s1+1);
    a[0]=r[0]=strlen(s1+1);
    for(int i=1;i<=a[0];i++) a[i]=r[i]=s1[a[0]-i+1]-'0';
    l[0]=ans[0]=1;
    while(compare(l,r)){
        add();
        div();
        times();
        if(compare(t,a)) left();
        else right();
    }
    for(int i=ans[0];i>0;i--) printf("%d",ans[i]);
    return 0;
}

12.高精度小技巧——壓位
當我們學完所有高精度運算的時候,我們依然會發現一個問題,就是它的數會給你很大,以至於當你一位一位進行運算的時候時間會超時!那怎麼辦呢?難道真的沒有解決的辦法了嗎?那你就太小看高精度了,其實解決這個問題的方法就是——壓位。壓位是什麼?聽我慢慢道來。我們可以發現導致高精度時間慢的原因是我們一位一位將這個數存了下來,那麼我們可不可以在數組中的一個位置不止存一個數位呢?答案是肯定的。你可以存兩位,三位,四位…其實我們壓位的時候最少都要壓十幾位,因爲你才壓幾位跟不壓位的時間有什麼區別呢?當我們壓位的時候,如果你要壓比如16位,那麼一定要記得開long long或int64類型,這樣你才存的下這個十幾位的數,還要注意,當我們輸出這個數的時候,當一個位置的數不滿你壓的位數時,比如你壓了16位,但是數組當前的位置的這個數只有1位!!!這說明了什麼?說明這個數是由前面的15個0和最後一位構成的,也就是說原來這個數是000000000000000X,但是你只輸出了X,明顯答案是不對的,比如說一個數是5200000000000000001314,那麼你存的是a{ 1314,520000 },當輸出的時候如果你只是按照數組中的數字輸出,那麼你的答案是5200001314,發現了嗎?你少了中間的0!!!所以當我們輸出的時候,我們要判斷這個數是否是滿你壓的位數,如果不足,那麼就要補0,但是最高位除外。這就是壓位的基本思路了。

1.高精度壓位讀入處理
高精度壓位讀入處理Code

const int mod=1000000000000000;
char s[6666];
int a[6666];
 
 
int main(){
    scanf("%s",s+1);
    int len=strlen(s+1),st=1;
    for(int i=len;i>0;i--){
        x=x+(s[i]-'0')*st;
        st*=10;
        if(st==mod){
            st=1;x=0;
            a[++a[0]]=x;
        }
    }
}

2.高精度壓位輸出處理
高精度壓位輸出處理Code


const int mod=10000000000000000;
char s[6666];
int a[6666];
 
int main(){
 
    printf("%d",a[a[0]]);
    for(i=a[0];i>0;i--){
        t=a[i],l=0;
        while(t>0){
            t/=10;l++;
        }
        for(j=1;j<=15-l;j++) printf("0");
        printf("%d",a[i]);
    }
}
 

更多的題目實現需要用已有的知識靈活變通。
以上爲高精度算法大部分內容。希望你能有所收穫。

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