数论基础 (Foundation of Number Theory)

一、高精度

定义:支持更大整数间的运算的算法结构

实现核心:模拟竖式运算

反转存储:高精度数字一般用字符串表示,我们希望将其按位储存于数组中。注意到,我们进行竖式运算时希望按位对齐,而默认输入的字符串是按头对齐的。因此我们往往“反转储存”。

1、预处理

const int N = 105; // 根据题目要求灵活设置数组长度
int a[N], b[N], c[N], d[N];
void clear() {
    memset(a, 0, sizeof(a);
    memset(b, 0, sizeof(b);
    memset(c, 0, sizeof(c);
    memset(d, 0, sizeof(d);
    // 清空数组
}

2、加法

1.相加。将两个加数对应位置上的数字相加。

2.进位。判断数码和是否大于10,是则更高一位增加1,当前位减少10。

void add(int a[], int b[], int c[])
{
    clear();
    for (int i = 1;i < N; i++)
    {
        c[i] += a[i] + b[i];
        if (c[i] >= 10)
        {
            c[i + 1] += 1;
            c[i] -= 10;
        }
    }
}

3、减法

 1.判断符号:比较被减数和减数的大小,若被减数较小则输出 " - " 并将二者互换。

2.相减。将两个加数对应位置上的数字相减。

3.借位。判断差是否小于 0,是则更高一位减少1,当前位增加10。

void sub(int a[], int b[], int c[])
{
    clear();
    for (int i = N; i >= 1; i--)
    {
        if (a[i] < b[i])
        {
            printf("-");
            sub(b, a, c);
            return;
        }
        else if (a[i] > b[i]) break;
    } // 判断被减数和减数的大小
    for (int i = 0; i < N; i++)
    {
        c[i] += a[i] - b[i];
        if (c[i] < 0) // 借位
        {
            c[i + 1] -= 1;
            c[i] += 10;
        }
    }
} 

4、乘法

考虑乘数 a,b 以及乘积 c,注意到a[ i ] * b[ j ]应该位于C[ i + j ],遍历 i,j 后将统一进位即可(用于储存答案的数组 c 长度要大于 i + j )。

void mul(int a[], int b[], int c[])
{
    clear();
    for (int i = 1; i < N; i++)
    {
        for (int j = 1;j < N; j++) c[i + j - 1] += a[i] * b[j];
        if (c[i] >= 10)
        {
            c[i + 1] += c[i] / 10;
            c[i] %= 10;
        }
    }
}

5、除法

找到被除数恰好够除时的最低位作为商的最高位,不断地做减法。

1.被除数较小时 (能用 long long 存下)

void div()
{
    int d; // 用于储存余数
    for (int i = 1;i <= lena; i++) // lena代表a数组长度
    {
        c[i] = (10 * d + a[i]) / b; //c数组用于储存商
        d = (10 * d + a[i]) % b; //更新余数
    }
}

2.被除数较大时 (不能用 long long 存下)

//用于判断余数d是否能被除数减
bool greater_eq(int a[], int b[], int last_dg, int len)
{
    // last_dg+len的长度比b的长度长1
    if (a[last_dg + len] != 0) return true;
    for (int i = len - 1; i >= 1; i--)
    {
        if (a[last_dg + i] > b[i]) return true;
        if (a[last_dg + i] < b[i]) return false;
    }
    return true;
}

void div(int a[], int b[], int c[], int d[]) {
    clear(); // 清空数组
    //c数组用于储存结果,d数组用于存储余数
    int la, lb;
    for (la = N - 1; la > 1; la--) if (a[la - 1] != 0) break;
    for (lb = N - 1; lb > 1; lb--) if (b[lb - 1] != 0) break;
    // 得到a,b数组的长度
    if (lb == 1){
        puts("> <");
        return;
    } // lb = 1,说明除数等于零,不能相除
    for (int i = 1; i < la; i++) d[i] = a[i];
    for (int i = la - lb; i >= 0; i--)
    {
        //将余数d减到不能再减
        while (greater_eq(d, b, i, lb))
        {
            for (int j = 0; j < lb; j++)
            {
                d[i + j] -= b[j];
                if (d[i + j] < 0)
                {
                    d[i + j + 1] -= 1;
                    d[i + j] += 10;
                }
            }
            c[i] += 1;
        }
    }
}

6、压位

上述处理中数组的每一位存储了0 - 9,但对于 int 而言,这有些过于浪费了。注意到,对于不同进制的数据,竖式运算的处理是一致的。因此,我们不妨将 10 进制进化为 10000 进制。唯一不同的是,为了将结果表示成 10 进制,我们需要对输出做一定处理。

压位和不压位的高精度计算存在三点不同点(以下提到的压位都是压 4 位,即将 10 进制进化为 10000 进制):

(1)存储:不压位的话,vector 或者数组中每个数据是 0 - 9;压位以后,每个数据是 0 - 9999。

(2)计算过程:不压位的话,除数和模数都是 10;压位以后,除数和模数都是 10000。

(3)输出:不压位的话,直接输出;压位的话,需要格式化输出,最高位直接输出即可,其他位都需要输出4位数字,不足的前面补零。

二、快速幂

定义:在  O(logn) 的时间复杂度内计算 a^{n} 的小技巧

Q:如何计算 a^{n}(以 a = 5, n = 10 为例)

思路1:5 \times 5 = 25, 25 \times 5 = 125 \cdots

        要进行 9 次乘法运算,时间复杂度 O(n)

思路2:5^{5} = 5 \times 5 \times 5 \times 5 \times 5, 5^{10} = 5^{5} \times 5^{5}

        要进行 5 次乘法运算 O(\frac{n}{2})(其实也是 O(n) ,只是优化了常数)

思路3:5^{2} = 5 \times 5, 5^{5} = 5 \times 5^{2} \times 5^{2}, 5^{10} = 5^{5} \times 5^{5}

        4 次乘法运算,时间复杂度 O(logn)

        核心思想:将指数 n 不断二分

思路4:5^{2} = 5 \times 5, 5^{4} = 5^{2} \times 5^{2}, 5^{8} = 5^{4} \times 5^{4}, 5^{10} = 5^{2} \times 5^{8}

        4 次乘法运算,时间复杂度 O(logn)

        核心思想:将指数 n 表示成二进制

1、递归快速幂

a^{n} =\left\{\begin{matrix} a^{\frac{n}{2}} \times a^{\frac{n}{2}} \times a & n = 2k + 1\\ a^{\frac{n}{2}} \times a^{\frac{n}{2}} & n = 2k\\ 1 & n = 0\\ \end{matrix}\right.

核心思想:偶数直接折半,奇数减一后折半。以 n = 0 为递归出口。

long long qpow(long long a, long long n) {
    if (n == 0) return 1;
    long long res = qpow(a, n / 2);
    if (n % 2 == 1) return res * res * a;
    else return res * res;    
}

2、迭代快速幂

你可能感兴趣的:(学习笔记,算法,矩阵,c++,c语言)