Contest100000593 - 《算法筆記》5.6小節——數學問題->大整數運算

常用模板

大整數的存儲

模板如下:

struct bign // 大整數存儲結構體
{
    int len; // 記錄大整數的位數
    int d[1000]; // 存儲每一位的數的數組
    bign() // 構造函數,用於初始化
    {
        memset(d, 0,sizeof(d));
        len = 0;
    }
};

bign Change(char str[]) // 將輸入的大整數字符串轉換爲bign
{
    bign a;
    a.len = strlen(str); // bign的長度就是字符串的長度
    for (int i = 0; i < a.len; ++i)
        a.d[i] = str[a.len - i - 1] - '0'; // 逆着賦值
    return a;
}

大整數的比較

模板如下:

int Compare(bign a, bign b)
{
    if (a.len > b.len)
        return 1;
    else if (a.len < b.len)
        return -1;
    else
    {
        for (int i = a.len - 1; i >= 0; --i)
        {
            if (a.d[i] > b.d[i]) // 只有要一位a大,則a大
                return 1;
            else // 只要有一位a小,則a小
                return -1;
        }
    }
    return 0; // 兩數相等
}

大整數的四則運算

高精度加法a + b

如果a、b都是非負數可直接應用該模板。如果有一個是負數,則去掉負號採用高精度減法模板。如果都是負數,則去掉負號,應用該模板後添加一個負號。

bign Add(bign a, bign b) // 高精度a + b
{
    bign c;
    int carry = 0; // carry是進位
    for (int i = 0; i < a.len || i < b.len; ++i) // 以較長的位爲界限
    {
        int temp = a.d[i] + b.d[i] + carry; // 兩個對應位與進位相加
        c.d[c.len++] = temp % 10; // 個位數爲該位結果
        carry = temp / 10; // 十位數爲新的進位
    }
    if (carry) // 如果最後進位不爲0,則直接賦給結果的最高位
        c.d[c.len++] = carry;
    return c;
}
高精度減法a - b

使用該模板前需要比較兩個數的大小,如果被減數小於減數,即a < b,需要交換兩個變量,然後輸出負號,再使用模板。

bign Sub(bign a, bign b) // 高精度a - b
{
    bign c;
    for (int i = 0; i < a.len || i < b.len; ++i) // 以較長的位爲界限
    {
        if (a.d[i] < b.d[i]) // 如果不夠減
        {
            --a.d[i + 1]; // 向高位借位,即高位減1
            a.d[i] += 10;
        }
        c.d[c.len++] = a.d[i] - b.d[i]; // 減法結果爲當前位結果
    }
    while (c.len - 1 >= 1 && c.d[c.len - 1] == 0)
        --c.len; // 去除高位的0,即將位數減小,同時保留至少一位最低位
    return c;
}
高精度與低精度的乘法a * b

如果a和b中存在負數,先記錄下其負號,應用模板後添加負號。

bign Multi(bign a, int b) // 高精度a * b
{
    bign c;
    int carry = 0; // carry是進位
    for (int i = 0; i < a.len; ++i)
    {
        int temp = a.d[i] * b + carry;
        c.d[c.len++] = temp % 10; // 個位數爲該位結果
        carry = temp / 10; // 高位部分作爲新的進位
    }
    while (carry) // 和加法不一樣,乘法的進位可能不止一位,因此用while
    {
        c.d[c.len++] = carry % 10;
        carry /= 10;
    }
    return c;
}
高精度與低精度的除法a / b

考慮到函數每次只能返回一個數據,而很多題目裏會經常要求得到餘數,因此把餘數寫成引用的形式直接作爲參數傳入,或是把r設成全局變量。

bign Divide(bign a, int b, int &r) // 高精度a / b
{
    bign c;
    c.len = a.len; // 被除數的每一位和商的每一位是一一對應的,因此先令長度相等
    for (int i = a.len - 1; i >= 0; --i) // 從高位開始做除法
    {
        r = r * 10 + a.d[i]; // 和上一位遺留的餘數組合
        if (r < b)
            c.d[i] = 0; // 不夠除,該位爲0
        else // 夠除
        {
            c.d[i] = r / b; // 商
            r = r % b; // 獲得新的餘數
        }
    }
    while (c.len - 1 >= 1 && c.d[c.len - 1] == 0)
        --c.len; // 去除高位的0,即將位數減小,同時保留至少一位最低位
    return c;
}
高精度除法應用於進制轉換

模板如下:

bign Divide(bign a, int b, int &r, int m) // 在m進制中做a / b
{
    bign c;
    c.len = a.len;
    for (int i = a.len - 1; i >= 0; --i)
    {
        r = r * m + a.d[i];
        if (r < b)
            c.d[i] = 0;
        else
        {
            c.d[i] = r / b;
            r = r % b;
        }
    }
    while (c.len - 1 >= 1 && c.d[c.len - 1] == 0)
        --c.len;
    return c;
}

問題 A: a+b

題目描述

實現一個加法器,使其能夠輸出a+b的值。

輸入

輸入包括兩個數a和b,其中a和b的位數不超過1000位。

輸出

可能有多組測試數據,對於每組數據,
輸出a+b的值。

樣例輸入

6 8
2000000000 30000000000000000000

樣例輸出

14
30000000002000000000

Note

這一題足足改了我一個多小時,這麼簡單的一道題目,你們一定不會相信一直不AC的原因居然在結構體定義中,構造函數裏面的順序可以隨便,但是len的定義必須放在數組前面!!否則一定會出錯!!我真的!!!無語了,什麼OJ系統!!!

#include <cstdio>
#include <cstring>
using namespace std;

struct bign // 大整數存儲結構體
{
    int len; // 記錄大整數的位數
    int d[1000]; // 存儲每一位的數的數組
    bign() // 構造函數,用於初始化
    {
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

bign Change(char str[]) // 將輸入的大整數字符串轉換爲bign
{
    bign a;
    a.len = strlen(str); // bign的長度就是字符串的長度
    for (int i = 0; i < a.len; ++i)
        a.d[i] = str[a.len - i - 1] - '0'; // 逆着賦值
    return a;
}

bign Add(bign a, bign b) // 高精度a + b
{
    bign c;
    int carry = 0; // carry是進位
    for (int i = 0; i < a.len || i < b.len; ++i) // 以較長的位爲界限
    {
        int temp = a.d[i] + b.d[i] + carry; // 兩個對應位與進位相加
        c.d[c.len++] = temp % 10; // 個位數爲該位結果
        carry = temp / 10; // 十位數爲新的進位
    }
    if (carry != 0) // 如果最後進位不爲0,則直接賦給結果的最高位
        c.d[c.len++] = carry;
    return c;
}

int main()
{
    char A[1010], B[1010];
    while (scanf("%s %s", A, B) != EOF)
    {
        bign a = Change(A);
        bign b = Change(B);
        bign c = Add(a, b);
        for (int i = c.len - 1; i >= 0; --i)
            printf("%d", c.d[i]);
        printf("\n");
    }

    return 0;
}

問題 B: N的階乘

題目描述

輸入一個正整數N,輸出N的階乘。

輸入

正整數N(0<=N<=1000)

輸出

輸入可能包括多組數據,對於每一組輸入數據,輸出N的階乘

樣例輸入

0
4
7

樣例輸出

1
24
5040

Note

位數數組開大點。

#include <iostream>
#include <cstring>
using namespace std;

struct bign // 大整數存儲結構體
{
    int len; // 記錄大整數的位數
    int d[100000]; // 存儲每一位的數的數組
    bign() // 構造函數,用於初始化
    {
        memset(d, 0,sizeof(d));
        len = 0;
    }
};

bign Multi(bign a, int b) // 高精度a * b
{
    bign c;
    int carry = 0; // carry是進位
    for (int i = 0; i < a.len; ++i)
    {
        int temp = a.d[i] * b + carry;
        c.d[c.len++] = temp % 10; // 個位數爲該位結果
        carry = temp / 10; // 高位部分作爲新的進位
    }
    while (carry) // 和加法不一樣,乘法的進位可能不止一位,因此用while
    {
        c.d[c.len++] = carry % 10;
        carry /= 10;
    }
    return c;
}

int main()
{
    int n;
    while (cin >> n)
    {
        bign a;
        a.len = 1; // 初始化
        a.d[0] = 1;
        for (int i = 2; i <= n; ++i)
            a = Multi(a, i);
        for (int i = a.len - 1; i >= 0; --i)
            cout << a.d[i];
        cout << endl;
    }

    return 0;
}

問題 C: 浮點數加法

題目描述

求2個浮點數相加的和
題目中輸入輸出中出現浮點數都有如下的形式:
P1P2…Pi.Q1Q2…Qj
對於整數部分,P1P2…Pi是一個非負整數
對於小數部分,Qj不等於0

輸入

對於每組案例,第1行是測試數據的組數n,每組測試數據佔2行,分別是兩個加數。
每組測試數據之間有一個空行,每行數據不超過100個字符

輸出

每組案例是n行,每組測試數據有一行輸出是相應的和。
輸出保證一定是一個小數部分不爲0的浮點數

樣例輸入

2
3.756
90.564

4543.5435
43.25

樣例輸出

94.32
4586.7935

Note

修修改改了一天,終於寫出了一個較爲簡潔且易懂的代碼,基本思路是類比浮點數的相加。用兩個更大的字符串存儲a,b,但是要小數點對齊的存進去。注意,更大的字符串長度至少要分配200,因爲存在兩個極端情況:整數98位小數一位或者整數一位小數98位,這樣對齊後相加的結果需要一個至少201(想想爲什麼是201)長度的字符串來存儲。具體實現看下面:

  1. 用p1、p2分別標記a、b的小數點位置。
  2. 定義三個運算字符串用於模擬加法,stc存儲運算的結果。別忘記初始化,加小數點以及加上終止符,要不然會出錯。字符串的處理一定要謹慎,另外就是一定要把終止符算入長度內!
  3. 將a和b按照小數點地位置存入sta和stb。拿a和sta舉個例子,具體的做法就是,從小數點開始,用 i 和 j 往左右遍歷a,用ql和qr往左右遍歷sta,然後將a[i]或a[j]存入相應的sta[ql]或sta[qr]。
  4. 進行正常的相加操作。
  5. 計算後的結果很有可能頭尾存在大量的0,所以需要過濾0字符。用兩個while來標記第一個非0字符和最後一個非0字符的下標。然後輸出兩個下標內的所有字符即可。
#include <iostream>
#include <cstring>
using namespace std;

void F(int p, char s[], char a[]) // 將字符串a放到字符串s中間
{
    for (int i = p - 1, j = p + 1, ql = 123, qr = 125;;)
    {
        if (i >= 0)
            s[ql--] = a[i--];
        if (j < strlen(a))
            s[qr++] = a[j++];
        if (i < 0 && j >= strlen(a))
            break;
    }
}

int main()
{
    int n;
    char a[110], b[110];
    while (cin >> n) // 輸入案例數
    {
        while (n--)
        {
            cin >> a >> b; // 輸入兩個浮點數字符串
            int p1 = 0, p2 = 0; // p1,p2分別標記小數點的下標
            while (a[p1++] != '.');
            while (b[p2++] != '.');

            char sta[251], stb[251], stc[251];
            for (int i = 0; i < 250; ++i) // 初始化運算字符串
                sta[i] = stb[i] = stc[i] = '0';
            sta[124] = stb[124] = stc[124] = '.'; // 標記小數點的位置
            sta[250] = stb[250] = stc[250] = '\0'; // 給字符串加上終止符
            F(p1 - 1, sta, a);
            F(p2 - 1, stb, b);

            int carry = 0; // 進位
            for (int i = strlen(sta) - 1; i >= 0; --i) // 進行加法運算
            {
                if (sta[i] != '.')
                {
                    int temp = (sta[i] - '0') + (stb[i] - '0') + carry;
                    stc[i] = (temp % 10) + '0';
                    carry = temp / 10;
                }
            }

            int head = 0, tail = strlen(stc) - 1; // flag = 1表明該0是首尾的無效0而不是浮點數中的0
            while (stc[head++] == '0');
            while (stc[tail--] == '0');
            for (int i = head - 1; i <= tail + 1; ++i)
                cout << stc[i];
            cout << endl;
        }
    }

    return 0;
}

問題 D: 進制轉換

題目描述

將M進制的數X轉換爲N進制的數輸出。

輸入

輸入的第一行包括兩個整數:M和N(2<=M,N<=36)。
下面的一行輸入一個數X,X是M進制的數,現在要求你將M進制的數X轉換成N進制的數輸出。

輸出

輸出X的N進製表示的數。

樣例輸入

10 2
11

樣例輸出

1011

提示

注意輸入時如有字母,則字母爲大寫,輸出時如有字母,則字母爲小寫。

Note

可以先做問題F再來做這一題就很好理解了。

#include <iostream>
#include <cstring>
using namespace std;

struct bign
{
    int len;
    int d[1000];
    bign()
    {
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

bign Change(char str[])
{
    bign a;
    a.len = strlen(str);
    for (int i = 0; i < a.len; ++i)
    {
        if (str[a.len - i - 1] <= '9')
            a.d[i] = str[a.len - i - 1] - '0';
        else if (str[a.len - i - 1] >= 'A')
            a.d[i] = str[a.len - i - 1] - 'A' + 10;
    }
    return a;
}

bign Divide(bign a, int b, int &r, int m) // 在m進制中做a / b
{
    bign c;
    c.len = a.len;
    for (int i = a.len - 1; i >= 0; --i)
    {
        r = r * m + a.d[i];
        if (r < b)
            c.d[i] = 0;
        else
        {
            c.d[i] = r / b;
            r = r % b;
        }
    }
    while (c.len - 1 >= 1 && c.d[c.len - 1] == 0)
        --c.len;
    return c;
}

int main()
{
    int m, n;
    char num[1000], ans[1000];
    while (cin >> m >> n)
    {
        cin >> num;
        int len = 0, r;
        bign a = Change(num);

        do {
            r = 0;
            a = Divide(a, n, r, m); // 在m進制內做除法
            if (r >= 0 && r <= 9)
                ans[len++] = r + '0';
            else if (r >= 10)
                ans[len++] = r - 10 + 'a';
        } while (!(a.len == 1 && a.d[0] == 0)); // 當ans爲0時終止循環
        ans[len] = '\0'; // 加上終止符

        for (int i = len - 1; i >= 0; --i)
            cout << ans[i];
        cout << endl;
    }

    return 0;
}

問題 E: 大整數排序

題目描述

對N個長度最長可達到1000的數進行排序。

輸入

輸入第一行爲一個整數N,(1<=N<=100)。
接下來的N行每行有一個數,數的長度範圍爲1<=len<=1000。
每個數都是一個正數,並且保證不包含前綴零。

輸出

可能有多組測試數據,對於每組數據,將給出的N個數從小到大進行排序,輸出排序後的結果,每個數佔一行。

樣例輸入

4
123
1234
12345
2345

樣例輸出

123
1234
2345
12345

Note

簡單的寫個cmp排序就好了。長度越長自然越大,長度相等時就比較字符串的大小。另外需要注意的是,sort函數是不能直接對二維字符串數組進行排序的。所以不要定義成num[100][1010]然後對num進行排序,在C++中無法編譯通過。具體的可參考這篇博客:sort對二維字符數組排序

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

struct number
{
    char data[1010];
} num[100];

bool cmp(number a, number b)
{
    if (strlen(a.data) != strlen(b.data))
        return strlen(a.data) < strlen(b.data);
    else
        return strcmp(a.data, b.data) < 0;
}

int main()
{
    int n;
    while (cin >> n)
    {
        for (int i = 0; i < n; ++i)
            cin >> num[i].data;
        sort(num, num + n, cmp);
        for (int i = 0; i < n; ++i)
            cout << num[i].data << endl;
    }

    return 0;
}

問題 F: 10進制 VS 2進制

題目描述

對於一個十進制數A,將A轉換爲二進制數,然後按位逆序排列,再轉換爲十進制數B,我們稱B爲A的二進制逆序數。
例如對於十進制數173,它的二進制形式爲10101101,逆序排列得到10110101,其十進制數爲181,181即爲173的二進制逆序數。

輸入

一個1000位(即10^999)以內的十進制數。

輸出

輸入的十進制數的二進制逆序數。

樣例輸入

985

樣例輸出

623

Note

這一題是大整數的計算,如果使用權值計算二進制轉十進制,就需要引入大整數的乘法的加法,會非常的麻煩。所以我們需要換方法計算。首先十進制轉二進制的方法很常規,就是不停地除以2,將得到的餘數記錄下來。類比過來,二進制轉十進制也是可以這麼做的。二進制轉十進制,是將二進制數x不停地除以10直到x等於0。將每一步的餘數按照二進制轉換爲十進制後存入數組,逆序輸出即爲二進制所對應的十進制數。具體的請看如下所示圖片:

二進制除法
除法中有幾個小細節是需要注意的:首先因爲是二進制除法,所以上商最高只能上1,其次就是二進制數x除以某數y,其實是除以y的二進制,所以是1010,;最後就是借位只能借2而不是10。

圖中的結果最後轉換爲的十進制即是(111 110)2×(10)10+(11)2=(62)10×(10)10+(3)10=623(111\ 110)_2 \times (10)_{10} + (11)_2 = (62)_{10} \times (10)_{10} + (3)_{10} = 623。有不懂的地方可以在評論區提出來。但是呢,這只是很簡單的商 x 除數 + 餘數 = 被除數。我們需要的是餘數,按照上面的思路,我們繼續往下做就能得到111110 / 1010 = 110…10,然後是110 / 1010 = 110。逆序後輸出的結果就是’110’ ‘10’ '11’即623.

最後就需要對大整數除法加以改進了。因爲是二進制除法,所以每一次向前借位只能借2,而且往往一位是不夠借的而是循環借好幾位才能湊出一位來。因此我們可對大整數除法的模板進行改進,添加一個進制的參數即可實現任意進制轉任意進制。

還有一點需要提醒的是,儘管模擬的是二進制的除法,但是實際上觀察Divide函數可以知道,我們的比較仍然是基於十進制的比較。這一點需要同學們思考一下了。

#include <iostream>
#include <cstring>
using namespace std;

struct bign
{
    int len;
    int d[1000];
    bign()
    {
        memset(d, 0, sizeof(d));
        len = 0;
    }
};

bign Change(char str[])
{
    bign a;
    a.len = strlen(str);
    for (int i = 0; i < a.len; ++i)
        a.d[i] = str[a.len - i - 1] - '0';
    return a;
}

bign Divide(bign a, int b, int &r, int m) // 在m進制中做a / b
{
    bign c;
    c.len = a.len;
    for (int i = a.len - 1; i >= 0; --i)
    {
        r = r * m + a.d[i];
        if (r < b)
            c.d[i] = 0;
        else
        {
            c.d[i] = r / b;
            r = r % b;
        }
    }
    while (c.len - 1 >= 1 && c.d[c.len - 1] == 0)
        --c.len;
    return c;
}

int main()
{
    char dec[1010], bin[3000];
    while (cin >> dec)
    {
        int len = 0, r;
        bign ans = Change(dec);
        do {
            r = 0;
            ans = Divide(ans, 2, r, 10); // 在十進制內做除法,做ans / 2
            bin[len++] = r + '0';
        } while (!(ans.len == 1 && ans.d[0] == 0)); // 當ans爲0時終止循環
        bin[len] = '\0'; // 加上終止符

        ans = Change(bin);
        len = 0;
        do {
            r = 0;
            ans = Divide(ans, 10, r, 2); // 在二進制內做除法,做ans / 10
            bin[len++] = r + '0';
        } while (!(ans.len == 1 && ans.d[0] == 0));
        bin[len] = '\0';

        for (int i = len - 1; i >= 0; --i)
            cout << bin[i];
        cout << endl;
    }

    return 0;
}

一定要自己寫一遍哦~~~

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