1. 程式人生 > >高精度演算法-壓位

高精度演算法-壓位

我們之前做過大整數類的運算的題目
大整數乘法
大整數加法
這個方法看似是無敵的,,,
但是那麼如果是一個10000^10000位的資料呢?
陣列根本開不到這麼大的。。。
有這樣的題目嗎?
傳送門

這時候我們就需要壓位了。。。
還記得我們儲存數字的方式?
舉個栗子:1234 + 1234這是之前的儲存方式
這裡寫圖片描述
實際上我們一個數組空間(int)是可以儲存 2147483647 以下的數字 這樣非常浪費 而且以上儲存方式需要按位相加最後的計算次數是4
現在考慮這樣的儲存方式
這裡寫圖片描述
這樣相對上面的儲存方式來說利用度較高
理解方式:考慮二進位制 十進位制 十六進位制 的計算方式 滿2進1 滿10進1 滿16進1
那麼以上的儲存方式請理解為100進位制的計算 也就是滿100進1
按照這樣儲存 按照(二)的規則計算 就是把1和2 3和4壓到了同一個陣列空間裡 叫做壓位
這樣儲存的數字計算次數就變成了2 很厲害的優化……
那我們為什麼不這樣呢
沒錯這是10000進位制……
計算次數變成了1 其實仔細看一下就知道壓位的思想就是把本來不用高精的計算儘量的擴大
來達到減少計算次數的目的
特殊的輸出
在最後的輸出答案部分 需要一些處理(下面假設寫壓P位的高精(也就是把P個數壓到同一個陣列空間裡))
一句話概括:除最高位外,一切不足P位的數字輸出時需要在前面補上0使得滿足P位
比如壓2位 2 + 99 為例 最後會用到兩個陣列空間(一個放1,一個放1…)但是這兩個一代表的意義不同,
第二個可以原樣輸出因為這是最高位(即整個數的長度modP後得到的剩餘部分 這一部分是獨立於其他部分的 如果補上0 最後就是0101 是不對的),第一個就需要補上一個0來使得輸出101而不是11

模板程式碼

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<map>
#include<set>
#include<queue>
using namespace std;


const int power = 4
; //每次運算的位數為10的power次方,在這裡定義為了方便程式實現 const int base = 10000; //10的power次方。 //要壓位的時候,只需改power 和 base即可,如壓萬位高精,那麼power = 4, base = 10000 const int MAXL = 10001; //陣列的長度。 char a[MAXL], b[MAXL], aa[MAXL]; struct num { int a[MAXL]; num() { memset(a, 0, sizeof(a)); } //初始化
num(char *s) //將一個字串初始化為高精度數 { memset(a, 0, sizeof(a)); int len = strlen(s); a[0] = (len+power-1) / power; //數的長度 for (int i=0, t=0, w; i < len ;w *= 10, ++i) { if (i % power == 0) { w = 1, ++t; } a[t] += w * (s[i]-'0'); } //初始化陣列,這裡自己模擬一下,應該很容易懂的~ } void add(int k) { if (k || a[0]) a[ ++a[0] ] = k; } //在末尾新增一個數,除法的時候要用到 void re() { reverse(a+1, a+a[0]+1); } //把數反過來,除法的時候要用到 void print() //列印此高精度數 { printf("%d", a[ a[0] ]); //先列印最高位,為了壓位 或者 該高精度數為0 考慮 for (int i = a[0]-1;i > 0;--i) printf("%0*d", power, a[i]); //這裡"%0*d", power的意思是,必須輸出power位,不夠則前面用0補足 printf("\n"); } } p,q,ans,r; bool operator < (const num &p, const num &q) //判斷小於關係,除法的時候有用 { if (p.a[0] < q.a[0]) return true; if (p.a[0] > q.a[0]) return false; for (int i = p.a[0];i > 0;--i) { if (p.a[i] != q.a[i]) return p.a[i] < q.a[i]; } return false; } num operator + (const num &p, const num &q) //加法,不用多說了吧,模擬一遍,很容易懂 { num c; c.a[0] = max(p.a[0], q.a[0]); for (int i = 1;i <= c.a[0];++i) { c.a[i] += p.a[i] + q.a[i]; c.a[i+1] += c.a[i] / base; c.a[i] %= base; } if (c.a[ c.a[0]+1 ]) ++c.a[0]; return c; } num operator - (const num &p, const num &q) //減法,也不用多說,模擬一遍,很容易懂 { num c = p; for (int i = 1;i <= c.a[0];++i) { c.a[i] -= q.a[i]; if (c.a[i] < 0) { c.a[i] += base; --c.a[i+1]; } } while (c.a[0] > 0 && !c.a[ c.a[0] ]) --c.a[0]; //我的習慣是如果該數為0,那麼他的長度也是0,方便比較大小和在末尾新增數時的判斷。 return c; } num operator * (const num &p, const num &q) //乘法,還是模擬一遍。。其實高精度就是模擬人工四則運算! { num c; c.a[0] = p.a[0]+q.a[0]-1; for (int i = 1;i <= p.a[0];++i) for (int j = 1;j <= q.a[0];++j) { c.a[i+j-1] += p.a[i]*q.a[j]; c.a[i+j] += c.a[i+j-1] / base; c.a[i+j-1] %= base; } if (c.a[ c.a[0]+1 ]) ++c.a[0]; return c; } num operator / (const num &p, const num &q) //除法,這裡我稍微講解一下 { num x, y; for (int i = p.a[0];i >= 1;--i) //從最高位開始取數 { y.add(p.a[i]); //把數添到末尾(最低位),這時候是高位在前,低位在後 y.re(); //把數反過來,變為統一的儲存方式:低位在前,高位在後 while ( !(y < q) ) //大於等於除數的時候,如果小於的話,其實答案上的該位就是初始的“0” y = y - q, ++x.a[i]; //看能減幾個除數,減幾次,答案上該位就加幾次。 y.re(); //將數反過來,為下一次添數做準備 } x.a[0] = p.a[0]; while (x.a[0] > 0 && !x.a[x.a[0]]) --x.a[0]; return x; } int main() { while (true) {char flag1=getchar(); if (flag1=='0') break; scanf("%s",&a); char flag2=getchar(); while (flag2==' ') flag2=getchar(); scanf("%s\n",&b); memset(aa,0,sizeof(aa)); aa[0]='0'; //cout<<a<<" "<<b<<endl; reverse(a,a+strlen(a)); reverse(b,b+strlen(b)); reverse(aa,aa+strlen(aa)); p=num(a); q=num(b); r=num(aa); if (flag1=='+'&&flag2=='+') {if (q<p) {ans=p-q; ans.print(); } else {ans=q-p; if (r<ans) printf("-"); ans.print(); } } else if (flag1=='+'&&flag2=='-') {ans=p+q; ans.print(); } else if (flag1=='-'&&flag2=='+') {ans=p+q; if (r<ans) printf("-"); ans.print(); } else {if (q<p) {ans=p-q; if (r<ans) printf("-"); ans.print(); } else {ans=q-p; ans.print(); } } } return 0; }