高精度學習筆記 & 純高精度題的洛谷題解


前言

什么是高精度?
高精度是一種算法,用於計算各種大數的運算。
什么意思呢?
我舉個栗子。首先OI界有一句金玉良言:

即使題面出的水,只要數據夠變態,難度指數級上升。

對於著名的水題和各種神仙的裝弱工具A+B Problem,它的數據范圍是什么呢?

\(|a|,|b| \le 10^9\)

嗯,所以我們可以放心大膽的這么寫:

#include <iostream>
#include <cstdio>

int main() {
    int a,b;
    std :: cin >> a >> b;
    std :: cout << a + b << std :: endl;
    return 0;
}

但是,如果我把數據范圍改成這樣:

\(|a|,|b| \le 10^{18}\)

你就不能用int了,而得用long long

#include <iostream>
#include <cstdio>

int main() {
    long long a,b;
    std :: cin >> a >> b;
    std :: cout << a + b << std :: endl;
    return 0;
}

最后,我要是把數據范圍改成這樣:

\(0 \le a,b \le 10^{500}\)

這就是luogu p1601的原題。
你根本就找不到一種數據類型能存這樣大的數。
那怎么辦呢?自己造一種數據類型?
沒錯,就是這樣。這種算法,就叫做高精度。

面對這樣大的數,蟹蟹唯一想到的辦法,就是用數組存儲每一個位數。
但這樣,肯定不能直接加減乘除運算,我們得自定義。
怎么自定義呢?
想想我們小時候求加減乘除的方法。沒錯,列豎式。
同樣的,這里我們也用兩個需要進行運算的數進行豎式模擬運算。這種方法雖然笨,但真的沒有什么別的方法了。
怎么來寫高精度呢?具體的豎式應該如何去操作呢?
請接着看。

說明一下,現在noip很少考到高精度了,但是我們為了保險,還是學習一下高精度。學總比不學強嘛。
再說明:本教程中的高精度只涉及到非負整數。負數和浮點數本教程中不考慮。

正式開始(最初版本)

准備工作

首先我們需要准備好程序構架。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cassert>

const int maxn = 4005;//運算時最多的位數,也就是說這個值取決於數據范圍中運算過程中數值可能的最大位數。

int main() {

}

然后定義一個結構體。這是一種高階玩法,到頭來我們可以直接用ubigint定義數,來進行高精度的運算,到時候直接加加減減就ok。

struct ubigint() {

}

賦值運算符

首先我們來看一些基本的賦值:

    int s[maxn], len;
    //一些基礎的東西
    ubigint() { memset(s, 0, sizeof(s)); }
    ubigint(int num) { *this = num; }
    ubigint(char *num) { *this = num; }
    
    ubigint operator = (int num) {
        char c[maxn];
        sprintf(c, "%d", num);
        *this = c;
        return *this;
    }

    ubigint operator = (const char *num) {
        len = strlen(num);
        for(int i = 0; i < len; i++)
            s[i] = num[len - i - 1] - '0';//請注意這里是倒過來賦值的,也就是說得到的數組其實直接輸出,數是反着的。為什么呢?一般我們列豎式都是從后往前的,所以我們把數組進行倒序處理,這樣我們在程序中就可以正序遍歷了。
        return *this;
    }

這些都是最基本的定義賦值之類的東西,我不再贅述。

關系運算符

接下來我們來重載一些關系運算符:

    //關系運算符
    bool operator < (const ubigint &b) const {
        if(len != b.len) return len < b.len;//比位數
        for(int i = len - 1; i >= 0; i--)//諸位比較
            if(s[i] != b.s[i])
                return s[i] < b.s[i];
        return false;
    }
    bool operator > (const ubigint &b) const {return b < *this; }
    bool operator <= (const ubigint &b) const {return !(b < *this);}
    bool operator >= (const ubigint &b) const {return !(*this < b);}
    bool operator == (const ubigint &b) const {return !(b < *this) && !(b > *this);}
    bool operator != (const ubigint &b) const {return b < *this || *this < b;}

我解釋一下小於號。二年級數學中就有兩個數比較大小的方法,先比位數,后從高位到低位諸位比較。

位數就是兩個數組的長度,如果len < b.len證明小於號判斷成立,如果len > b.len說明小於號判斷不成立。

如果位數相等,從高位到低位諸位比較,如果當前數字不一樣,則對當前的數字的大小關系來判斷這兩個數的大小關系。

如果諸位比較完成,說明兩個數相等。不過這個時候是不滿足小於的,所以返回false。

然后其他的就是各種套用小於號,因為我懶得在寫一遍了qaq(不要打蟹蟹呀嚶嚶嚶)

算術運算符

加法

首先先來康加法。
首先小學我們是咋算加法的呢?對了,列豎式。
我們可以從最后一位(再次提醒,請注意本教程中數組存儲是倒着的,所以程序中是從第一個字符開始遍歷)開始無腦加法,然后把得到的結果放在結果的對應位置。
嗎?
你在想什么啊,進位我們還得考慮。
於是,我們就可以相加,如果沒進位直接放,如果進位了把得到的結果%10放到結果的那個位置,然后那個位置的前面那個位置加上1(表示進位)。
但是我們顯然還漏掉了一種情況,當兩個加數的位數不相同。其實這個問題根本不算什么問題,只需要特判好了。
這樣就不難得到代碼了:

    ubigint operator + (const ubigint &b) const {
        ubigint res;
        res.len = 0;
        for(int i = 0, x = 0; x || i < len || i < b.len; i++, x /= 10) {
            if(i < len) x += s[i];
            if(i < b.len) x += b.s[i];
            res.s[res.len++] = x % 10;
        }
        return res;
    }

減法

然后是減法。
減法其實和加法差不多,也是按位相減,如果不夠,借1.
順便說明一下,這里的減法不支持被減數小於減數的情況,也就是結果必須大於0.

    ubigint operator - (const ubigint &b) const {
        assert(*this >= b);
        ubigint res;
        res.len = 0;
        int x;
        for(int i = 0, g = 0; i < len; i++) {
            x = s[i] - g;
            if(i < b.len) x -= b.s[i];
            if(x >= 0) g = 0;
            else {
                x += 10;
                g = 1;
            }
            res.s[res.len++] = x;
        }
        res.clear_zero();//注意到這里了沒有?
        return res;
    }

如果你是一個細心的人,會發現上面多了一個clear_zero()
這是什么?
一般在高精度運算中,可能會出現結果是正確的,但是結果之前有好幾個0.這種0叫做前導0,高精度運算中常常不能避免,所以我們需要定義一個clear_zero()函數去掉這些多余的前導0.
怎么清呢?我們只需要定義這樣的代碼:

    void clear_zero() {
        while(len > 1 && !s[len - 1]) len--;
    }

你可能會問了,為什么是len > 1,而不是len > 0呢?
前導0不是遇到不是0的情況才停的,你想啊,如果這個數是0,那這個0可不算是前導0.所以我們不清個位,個位都有前導0只能說明這個數是0,需要特殊對待。

乘法

乘法這點就比較難了,蟹蟹陪你耐心分析。(其實也不是很難辣,放松心態呀qaq)

小學我們是怎么學的?每一位按位相乘,相乘的結果再進行相加。
但存儲相乘的結果又是一筆開銷,我們不如思維跳躍一下,把每一位相乘的結果直接對應到res數組里,豈不美滋滋?

這里我們有一個定律,第一個數從后往前第i位數 和 第二個數從后往前第j位數 相乘 的結果 最后 會相加到 結果的 從后往前第i+j位上。
如果你不懂,不如先列幾個豎式,感受感受下?這里一定要弄懂哦。

但是進位也是個麻煩事。每一位的數在枚舉i,j的時候隨時都會變,隨時進位總覺得不好。我們還是都累積完了,再一一進位吧,這樣比較好。(個人習慣辣)

於是就有了如下的代碼:

    ubigint operator * (const ubigint &b) const {
        ubigint res;
        res.len = len + b.len;
        for(int i = 0; i < len; i++)
            for(int j = 0; j < b.len; j++)
                res.s[i + j] += s[i] * b.s[j];
        for(int i = 0; i < res.len - 1; i++) {
            res.s[i + 1] += res.s[i] / 10;
            res.s[i] %= 10;
        }
        res.clear_zero();
        return res;
    }

除法

其實除法就是乘法的逆運算,和乘法也差不多,就像加法和減法一樣。不過除法在試除這個方面是有技巧的,我們可以二分試除。

	ubigint operator / (const ubigint &b) const {
		assert(b > 0);
		ubigint res = *this,m;
		for(int i = len - 1; i >= 0; --i) {
			m = m * 10 + s[i];
			res.s[i] = midsearch(b, m);
			m = m - b * res.s[i];
		}
		res.clear_zero();
		return res;
	}

midsearch函數:

    int midsearch(const ubigint &b, const ubigint &m) const {
        int L = 0, R = 9, mid;
        while(1) {
            mid = (L + R) >> 1;
            if(b * mid <= m) {
                if(b * (mid + 1) > m) return mid; 
                else L = mid;
            }
            else R = mid;
        }    
    }

模法

除法都完了,但是別忘了,我們還有魔法。我不會告訴你這其實是%法的,%%% tql orz
取模運算其實特別簡單,把上述代碼中,最后return m;就可以了。

    ubigint operator % (const ubigint &b) const {
        assert(b > 0);
        ubigint res = *this;
        ubigint m;
        for(int i = len - 1; i >= 0; i--) {
            m = m * 10 + s[i];
            res.s[i] = midsearch(b,m);
            m = m - b * res.s[i];
        }
        m.clear_zero();
        return m;
    }

擴展賦值運算符

我們還可以通過以上的五則運算,擴展出+= -= *= /= %= 。 這就很簡單辣。

    ubigint& operator += (const ubigint &b) {*this = *this + b; return *this;}
    ubigint& operator -= (const ubigint &b) {*this = *this - b; return *this;}
    ubigint& operator *= (const ubigint &b) {*this = *this * b; return *this;}
    ubigint& operator /= (const ubigint &b) {*this = *this / b; return *this;}
    ubigint& operator %= (const ubigint &b) {*this = *this % b; return *this;}

輸入輸出流

最后的最后,我們搞定輸入輸出流吧。

std :: istream& operator >>(std :: istream &in, ubigint &x) {
    std :: string s;
    in >> s;
    x = s.c_str();
    return in;
}

std :: ostream& operator <<(std :: ostream &out,ubigint x) {
    out << x.to_str();
    return out;
}

哦對了差點忘記說,我們為了輸出ubigint這個類型,還需要定義一個to_str函數,把ubigint類型轉成std :: string型。很簡單辣。

    std :: string to_str() {
        std :: string res = "";
        for(int i = 0; i < len; i++) res = (char)(s[i] + '0') + res;
        return res;
    }

至此我們的ubigint就定義完了,拍張合照!

總體代碼

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cassert>

const int maxn = 30005;
struct ubigint {
    int s[maxn], len;
    //一些基礎的東西
    ubigint() { memset(s, 0, sizeof(s)); }
    ubigint(int num) { *this = num; }
    ubigint(char *num) { *this = num; }
    
    ubigint operator = (int num) {
        char c[maxn];
        sprintf(c, "%d", num);
        *this = c;
        return *this;
    }

    ubigint operator = (const char *num) {
        len = strlen(num);
        for(int i = 0; i < len; i++)
            s[i] = num[len - i - 1] - '0';
        return *this;
    }

    int midsearch(const ubigint &b, const ubigint &m) const {
        int L = 0, R = 9, mid;
        while(1) {
            mid = (L + R) >> 1;
            if(b * mid <= m) {
                if(b * (mid + 1) > m) return mid; 
                else L = mid;
            }
            else R = mid;
        }    
    }

    std :: string to_str() {
        std :: string res = "";
        for(int i = 0; i < len; i++) res = (char)(s[i] + '0') + res;
        return res;
    }

    void clear_zero() {
        while(len > 1 && !s[len - 1]) len--;
    }

    //關系運算符
    bool operator < (const ubigint &b) const {
        if(len != b.len) return len < b.len;
        for(int i = len - 1; i >= 0; i--)
            if(s[i] != b.s[i])
                return s[i] < b.s[i];
        return false;
    }
    bool operator > (const ubigint &b) const {return b < *this; }
    bool operator <= (const ubigint &b) const {return !(b < *this);}
    bool operator >= (const ubigint &b) const {return !(*this < b);}
    bool operator == (const ubigint &b) const {return !(b < *this) && !(b > *this);}
    bool operator != (const ubigint &b) const {return b < *this || *this < b;}

    ubigint operator + (const ubigint &b) const {
        ubigint res;
        res.len = 0;
        for(int i = 0, x = 0; x || i < len || i < b.len; i++, x /= 10) {
            if(i < len) x += s[i];
            if(i < b.len) x += b.s[i];
            res.s[res.len++] = x % 10;
        }
        return res;
    }

    ubigint operator - (const ubigint &b) const {
        assert(*this >= b);
        ubigint res;
        res.len = 0;
        int x;
        for(int i = 0, g = 0; i < len; i++) {
            x = s[i] - g;
            if(i < b.len) x -= b.s[i];
            if(x >= 0) g = 0;
            else {
                x += 10;
                g = 1;
            }
            res.s[res.len++] = x;
        }
        res.clear_zero();
        return res;
    }

    ubigint operator * (const ubigint &b) const {
        ubigint res;
        res.len = len + b.len;
        for(int i = 0; i < len; i++)
            for(int j = 0; j < b.len; j++)
                res.s[i + j] += s[i] * b.s[j];
        for(int i = 0; i < res.len - 1; i++) {
            res.s[i + 1] += res.s[i] / 10;
            res.s[i] %= 10;
        }
        res.clear_zero();
        return res;
    }

    ubigint operator / (const ubigint &b) const {
        assert(b > 0);
        ubigint res = *this,m;
        for(int i = len - 1; i >= 0; i--) {
            m = m * 10 + s[i];
            res.s[i] = midsearch(b, m);
            m = m - b * res.s[i];
        }
        res.clear_zero();
        return res;
    }

    ubigint operator % (const ubigint &b) const {
        assert(b > 0);
        ubigint res = *this;
        ubigint m;
        for(int i = len - 1; i >= 0; i--) {
            m = m * 10 + s[i];
            res.s[i] = midsearch(b,m);
            m = m - b * res.s[i];
        }
        m.clear_zero();
        return m;
    }

    ubigint& operator += (const ubigint &b) {*this = *this + b; return *this;}
    ubigint& operator -= (const ubigint &b) {*this = *this - b; return *this;}
    ubigint& operator *= (const ubigint &b) {*this = *this * b; return *this;}
    ubigint& operator /= (const ubigint &b) {*this = *this / b; return *this;}
    ubigint& operator %= (const ubigint &b) {*this = *this % b; return *this;}
};

std :: istream& operator >>(std :: istream &in, ubigint &x) {
    std :: string s;
    in >> s;
    x = s.c_str();
    return in;
}

std :: ostream& operator <<(std :: ostream &out,ubigint x) {
    out << x.to_str();
    return out;
}

int main() {

}

壓位優化

其實這樣的高精度我們還能繼續優化。來看下面這個例子:

顯然我們在ubigint類中,是一位一位加的。但是我們為什么不能這樣??

之前算四遍,現在算兩遍。我們可以想想,能不能直接算一遍呢?

行吧,這就是直接算了。
但是我們為什么不能這樣直接算一遍呢?這其實就是高精度存在的意義,根本不存在一種數據方式來算這么大的加法。比如以下就是一個例子:

這可不能直接算。但是我們有剛剛的方法,可以盡量的分割這兩個數:

這樣算的話,就變成了兩位兩位的算,算的次數減少了,就會有優化。
我們還可以壓4位:

算的方法就更少啦。
我們不妨思考一個問題,我們這樣分數,分得的每一個區域允許的最大位數是多少?
如果是int型,顯然是9位。因為int的范圍最大是2147483647,此時9位相加相減,不會爆炸。所以我們最好這樣:

這種方法,就叫做壓位。使用壓位的高精,就叫做壓位高精。
平常我們所說的壓x位,其實就是把數分成若干部分,使得每一個部分最大是x位數。(為什么是最大呢,因為有可能沒分完。)
有好奇心強的小伙伴可能會問了,壓位看起來算的次數減少,可每一個位置內置的加法算法不是也增多了嗎?這樣豈不是拆東牆補西牆?
我諤諤,C++內置的加法算法怎么說也是經過層層優化的,增加位數並不會浪費多長的時間,至少不像我們寫的高精度,要不然C++也走不到這樣高的巔峰。我們有更高的靠山,就盡量去靠。
代碼:

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2020-03-19 23:07:41  
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2020-03-19 01:07:24
 */

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <string>

int max(int a,int b) {
    return a > b ? a : b;
}

static const int base = 1000000000;
static const int width = 9;
    
struct ubigint {
    typedef long long ll;

    ll s[8005] = {0};
    int len = 1;

    ubigint(){}

    ubigint(const ubigint& x) {
        for(int i = 0; i < x.len; i++)
            this -> s[i] = x.s[i];
        this -> len = x.len;
    }
    
    ubigint(const int& x) {
        int tmp = x;
        this -> len = 0;
        do{
            this -> s[this -> len++] = tmp % base;
            tmp /= base;
        }while(tmp);
    }

    ubigint operator = (const std :: string& str) {
        std :: string tmp = str;
        while(tmp.length() > width) {
            this -> s[this -> len - 1] = atoi(tmp.substr(tmp.length() - width).c_str());
            tmp.erase(tmp.length() - width);
            this -> len++;
        }
        this -> s[this -> len - 1] = atoi(tmp.c_str());
        return *this;
    }

    void clear_zero() {
        while(this -> len > 1 && !(this -> s[this -> len - 1])) this -> len--;
    }

    ubigint operator = (const ubigint& x) {
        this -> len = x.len;
        for(register int i = 0; i < this -> len; i++)
            this -> s[i] = x.s[i];
        return *this;
    }

    bool operator < (const ubigint& b) const {
        if(this -> len != b.len) return this -> len < b.len;
        for(register int i = this -> len - 1; i >= 0; i--)
            if(this -> s[i] != b.s[i])
                return this -> s[i] < b.s[i];
        return false;
    }

    bool operator > (const ubigint& b) const {
        return b < *this;
    }

    bool operator <= (const ubigint& b) const {
        return !(b < *this);
    }

    bool operator >= (const ubigint& b) const {
        return !(*this < b);
    }

    bool operator != (const ubigint& b) const {
        return *this < b || b < *this;
    }

    bool operator == (const ubigint& b) const {
        return !(b < *this) && !(*this < b);
    }

    ubigint operator + (const ubigint& b) const {
        ubigint res;
        res.len = max(this -> len, b.len);
        for(register int i = 0, last = 0; i < res.len; i++) {
            res.s[i] = this -> s[i] + b.s[i] + last;
            last = res.s[i] / base;
            res.s[i] %= base;
            if(i == res.len - 1 && last) res.len++;
        }
        res.clear_zero();
        return res;
    }

    ubigint operator - (const ubigint& b) const {
        assert(*this >= b);
        ubigint res = *this;
        for(register int i = 0, last = 0; i < this -> len; i++) {
            res.s[i] -= b.s[i] + last;
            if(res.s[i] < 0) {
                res.s[i] += base;
                last = 1;
            }else last = 0;
        }
        res.clear_zero();
        return res;
    }

    ubigint operator * (const ubigint& b) const {
        if(*this == 0 || b == 0) return ubigint(0);
        //if(*this == 1) return b;
        //if(b == 1) return *this;
        ubigint res;
        res.len = this -> len + b.len + 1;
        for(register int i = 0; i < this -> len; i++) {
            for(register int j = 0; j < b.len; j++) {
                res.s[i + j] += this -> s[i] * b.s[j];
                res.s[i + j + 1] += res.s[i + j] / base;
                res.s[i + j] %= base;
            }
        }
        res.clear_zero();
        return res;
    }

    ubigint operator / (const ubigint& b) const {
        if(b > *this) return 0;
        //if(b == *this) return 1;
        ubigint res = 0, div = b, mod = *this;
        while(div * ubigint(base) <= *this) div *= ubigint(base);
        for(;;) {
            int l = 1, r = base, mid;
            if(mod >= div) {
                while(r > l + 1) {
                    int mid = l + r >> 1;
                    if(div * ubigint(mid) > mod) r = mid;
                    else l = mid;
                }
                mod -= div * ubigint(l);
                res.s[res.len - 1] += l;
            }

            if(div == b) break;
            res.len++;
            for(register int i = 1; i < div.len; i++)
                div.s[i - 1] = div.s[i];
            div.s[div.len - 1] = 0;
            div.len--;
        }
        std :: reverse(res.s, res.s + res.len);
        res.clear_zero();
        return res;
    }

    ubigint operator % (const ubigint& b) const {
        return *this - (*this / b * b);
    }

    ubigint operator += (const ubigint& b) {*this = *this + b; return *this;}
    ubigint operator -= (const ubigint& b) {*this = *this - b; return *this;}
    ubigint operator *= (const ubigint& b) {*this = *this * b; return *this;}
    ubigint operator /= (const ubigint& b) {*this = *this / b; return *this;}
    ubigint operator %= (const ubigint& b) {*this = *this % b; return *this;}
    
    friend std :: istream& operator >> (std :: istream& in, ubigint& b) {
        std :: string str;
        in >> str;
        b = str;
        return in;
    }

    friend std :: ostream& operator << (std :: ostream& out, ubigint b) {
        out << b.s[b.len - 1];
        for(register int i = b.len - 2; i >= 0; i--) {
            int div = base / 10;
            while(b.s[i] < div) {
                std :: cout << 0;
                div /= 10;
            }
            if(b.s[i]) out << b.s[i];
        }
        return out;
    }
};

int main() {
    ubigint a,b;
    std :: cin >> a >> b;
    std :: cout << a + b << std :: endl;
    if(a >= b) std :: cout << a - b << std :: endl;
    else std :: cout << '-' << b - a << std :: endl;
    std :: cout << a * b << std :: endl;
    std :: cout << a / b << std :: endl;
    std :: cout << a % b << std :: endl;
    return 0;    
}

這是靜態數組版本的,我們還可以用動態數組vector來重構代碼。不過這樣時間需要的就長啦(寫這個的時候懶得寫this ->了qvq)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cassert>
#include <vector>

int max(int a,int b) {
	return a > b ? a : b;
}

struct ubigint {
	typedef unsigned long long ull;

	static const int base = 100000000;
	static const int width = 8;
	
	std :: vector<int> s;

	ubigint& clear_zero() {
		while(!s.back() && s.size() > 1) s.pop_back();
		return *this;
	}
	
	ubigint(ull num = 0) {*this = num;}
	ubigint(std :: string str) {*this = str;}

	ubigint& operator = (ull num) {
		s.clear();
		do {
			s.push_back(num % base);
			num /= base;
		}while(num);
		return *this;
	}

	ubigint& operator = (const std :: string& str) {
		s.clear();
		int x;
		int len = (str.length() - 1) / width + 1;
		for(int i = 0; i < len; i++) {
			int endidx = str.length() - i * width;
			int startidx = max(0, endidx - width);
			int x;
			sscanf(str.substr(startidx, endidx - startidx).c_str(), "%d", &x);
			s.push_back(x);
		}
		return (*this).clear_zero();
	}

    bool operator < (const ubigint& b) const {
        if(s.size() != b.s.size()) return s.size() < b.s.size();
        for(int i = s.size() - 1; i >= 0; i--)
            if(s[i] != b.s[i])
                return s[i] < b.s[i];
        return false;
    }
    bool operator > (const ubigint& b) const {return b < *this; }
    bool operator <= (const ubigint& b) const {return !(b < *this);}
    bool operator >= (const ubigint& b) const {return !(*this < b);}
    bool operator == (const ubigint& b) const {return !(b < *this) && !(b > *this);}
    bool operator != (const ubigint& b) const {return b < *this || *this < b;}

    ubigint operator + (const ubigint& b) const {
        ubigint res;
        res.s.clear();
        for(int i = 0, x = 0; x || i < s.size() || i < b.s.size(); i++, x /= base) {
            if(i < s.size()) x += s[i];
            if(i < b.s.size()) x += b.s[i];
            res.s.push_back(x % base);
        }
        return res.clear_zero();
    }

	ubigint operator - (const ubigint& b) const {
		assert(*this >= b);
		ubigint res;
		res.s.clear();
		for(int i = 0, last = 0;last || i < s.size() || i < b.s.size();i++) {
			int x = s[i] + last;
			if(i < b.s.size()) x -= b.s[i];
			if(x < 0) {
				last = -1;
				x += base;
			}else last = 0;
			res.s.push_back(x);
		}
		return res.clear_zero();
	}

	ubigint operator * (const ubigint& b) const {
		std :: vector<ull> tmp(s.size() + b.s.size(),0);
		ubigint res;
		res.s.clear();
		for(int i = 0; i < s.size(); i++)
			for(int j = 0; j < b.s.size(); j++)
				tmp[i + j] += ull(s[i]) * b.s[j];
		
        ull last = 0;
		for(int i = 0; last || i < tmp.size(); i++) {
			ull x = tmp[i] + last;
			res.s.push_back(x % base);
			last = x / base;
		}
		return res.clear_zero();
	}

	int midsearch(const ubigint& b, const ubigint& m) const {
		int l = 0, r = base - 1;
		while(1) {
			int mid = l + r >> 1;
			if(b * mid <= m && b * (mid + 1) > m) return mid;
			if(b * mid <= m) l = mid;
			else r = mid;
		}
	}

	ubigint operator / (const ubigint& b) const {
		assert(b > 0);
		ubigint res = *this, mod;
		for(int i = s.size() - 1; i >= 0; i--) {
			mod = mod * base + s[i];
			res.s[i] = midsearch(b, mod);
			mod -= b * res.s[i];
		}
		return res.clear_zero();
	}

	ubigint operator % (const ubigint& b) const {
		assert(b > 0);
		ubigint res = *this, mod;
		for(int i = s.size() - 1; i >= 0; i--) {
			mod = mod * base + s[i];
			res.s[i] = midsearch(b, mod);
			mod -= b * res.s[i];
		}
		return mod.clear_zero();
	}

	ubigint& operator += (const ubigint& b) {*this = *this + b; return *this;}
	ubigint& operator -= (const ubigint& b) {*this = *this - b; return *this;}
	ubigint& operator *= (const ubigint& b) {*this = *this * b; return *this;}
	ubigint& operator /= (const ubigint& b) {*this = *this / b; return *this;}
	ubigint& operator %= (const ubigint& b) {*this = *this % b; return *this;}
	

	friend std :: istream& operator >> (std :: istream& in, ubigint& x) {
		std :: string str;
		if(!(in >> str)) return in;
		x = str;
		return in;
	}

	friend std :: ostream& operator << (std :: ostream& out, ubigint x) {
		out << x.s.back();
		for(int i = x.s.size() - 2; i >= 0; i--) {
			char buf[20];
			sprintf(buf, "%08d", x.s[i]);
			for(int j = 0; j < strlen(buf); j++)
				out << buf[j];
		}
		return out;
	}
};

ubigint a,b;
int main() {
	
	std :: cin >> a >> b;
	std :: cout << a + b << std :: endl;
	if(a >= b) std :: cout << a - b << std :: endl;
    else std :: cout << "-" << b - a << std :: endl;
	std :: cout << a * b << std :: endl;
	std :: cout << a / b << std :: endl;
	std :: cout << a % b << std :: endl;
	return 0;
}

當然這樣的高精度還存在一些不足,比如不支持負數,乘法沒有FFT優化等。但是考慮到競賽中不會出現這么duliu的高精度,本文不作處理。

小試牛刀

最后我們來小試幾道牛刀吧。
luogu p1601(高精A + B):

int main() {
    ubigint a, b;
    std :: cin >> a >> b;
    std :: cout << a + b << std :: endl;
    return 0;
}

luogu p2142(高精A - B):

這道題需要負數誒!不過沒關系,我們有一個小伎倆:

int main() {
    ubigint a,b;
    std :: cin >> a >> b;
    if(a < b) std :: cout << "-" << b - a << std :: endl;
    else std :: cout << a - b<< std :: endl;
    return 0;
}

是不是既簡單又方便的偷懶方式呢~
luogu p1303(高精A * B):

int main() {
    ubigint a, b;
    std :: cin >> a >> b;
    std :: cout << a * b << std :: endl;
    return 0;
}

luogu p1480 & luogu p2005(高精A / B):
后面的那個是前面的那個的升級版。雙倍經驗美滋滋

int main() {
    ubigint a,b;
    std :: cin >> a >> b;
    std :: cout << a / b << std :: endl;
    return 0;    
}

luogu p1932(高精合集):

int main() {
    ubigint a,b;
    std :: cin >> a >> b;
    std :: cout << a + b << std :: endl;
    if(a >= b) std :: cout << a - b << std :: endl;
    else std :: cout << '-' << b - a << std :: endl;
    std :: cout << a * b << std :: endl;
    std :: cout << a / b << std :: endl;
    std :: cout << a % b << std :: endl;
    return 0;    
}

因為原題中的減法涉及到了負數,所以減法做了這樣的處理qvq(別打我啊qvq)
而且注意,親測本題如果用動態數組版本是無法AC的,結果是80TLE,必須得吸氧才能AC。所以這道題請使用靜態數組版本。
luogu p1009(階乘之和):

int main() {
	int n;
    ubigint ans;
    std :: cin >> n;
    for(int i = 1; i <= n; i++) {
        ubigint mul = 1;
        for(int j = 2; j <= i; j++) 
            mul *= j;
        ans += mul;
    }
    std :: cout << ans << std :: endl;
    return 0;
}

luogu p1591(階乘數碼,一個數的階乘中某個數碼出現了幾次):
直接按照題意模擬,沒啥好說的吧qaq。

int main() {
	int T;
    std :: cin >> T;
    while(T--) {
        int n, num, ans = 0;
        std :: cin >> n >> num;
        ubigint res = 1;
        for(int i = 2; i <= n; i++)
            res *= i;
        ull tmp = res.s.back();
        while(tmp) {
            if(tmp % 10 == num) ans++;
            tmp /= 10;
        }
        for(int i = res.s.size() - 2; i >= 0; i--) {
			char buf[20];
			sprintf(buf, "%08d", res.s[i]);
			for(int j = 0; j < strlen(buf); j++)
				if(buf[j] - '0' == num) ans++;
		}
        std :: cout << ans << std :: endl;
    }
}

luogu p1255(數樓梯):
此題的主要思路是遞推,套上了高精度的外衣而已。但是這個過於簡單就歸於高精模板里吧。

const int maxn = 5005;
int n;
ubigint a[maxn] = {0, 1, 2};

int main() {
    std :: cin >> n;
    if(n <= 2) {
        std :: cout << a[n] << std :: endl;
        return 0;
    }
    for(int i = 3; i <= n; i++)
        a[i] = a[i - 1] + a[i - 2];
    std :: cout << a[n] << std :: endl;
    return 0;
}

qvq別忘了點個贊再走呀qvq!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM