从零开始的算法学习生活——①高精度运算

Posted 我带你们打代码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始的算法学习生活——①高精度运算相关的知识,希望对你有一定的参考价值。

文章目录


前言

上一篇文章简单提及了高精度加法运算,那么这次让我们看看高精度乘法高精度模板相应的实现吧!


一、高精度乘法

1.分析

回想一下A+B高精的实现,我们从竖式加法中得到启发,发现了数组实现大数加法的本质,那么这里我们不妨从竖式乘法的角度分析,通过下表来观察其实质。

举个例子,342*456=155,952,下表是每一位的运算过程。

第5位第4位第3位第2位第1位第0位
a342
b456
a*b[0]182412
a*b[1]152010
a*b[2]12168
加和1231463412
处理进位155952
结果155952

仔细观察可以发现,对于某两位之间的乘法运算结果a[i]*b[j],其对于最终结果的贡献都在第i+j位上。利用这一条性质,我们可以先将所有的贡献计算出来,最后进行进位处理,提升了运算效率。

2.例题

1.题面

传送门: 洛谷P1303(A*B Problem)

2.AC代码

附上AC代码

#include<iostream>
#include<string>
using namespace std;
const int max_n = 2002;//最大位数
int a[max_n],b[max_n],c[max_n];
int main()

    string A,B;
    cin>>A>>B;
    int lenA = A.length(),lenB = B.length();
    for (int i = 0,j = lenA-1; j>=0; i++,j--)
        a[i] = A[j] - '0';
    for (int i = 0,j = lenB-1; j>=0; i++,j--)
        b[i] = B[j] - '0';
    for (int i = 0; i < lenA; i++)//计算每一位的贡献
        for (int j = 0; j < lenB; j++)
            c[i+j] += a[i] * b[j];
        
    
    int len = lenA + lenB;//两数乘积的最大位数不超过两数位数之和
    for(int i = 0;i < len;i++)//从低位至高位依次处理进位
        c[i + 1] += c[i] / 10;
        c[i] %= 10;
    
    while (!c[len-1])
        len--;//去除多余0位
    
    for (int i = max(0,len-1);i >=0;i--)//这里使用max函数是为了处理乘数为0的情况
    
        cout<<c[i];
    //输出
    return 0;


PS:此代码的时间复杂度为O(n²),n为数的位数。若要进行优化,则可以通过快速傅里叶变换来加速高精度乘法,将其优化为O(nlogn),由于比较复杂,在此不作深入说明。


二、高精度模板

1.分析

到此,我们已经了解了用数组模拟大数并进行加法与乘法的本质与方法,那么有一个很自然的问题,能不能使用一个模板,使得每次需要使用高精度运算时,或者同时需要使用加法与乘法运算时,能够使用同一个模板进行运算呢?

根据C++的特性,我们可以通过封装结构体,并重载运算符的方式来实现一个高精度模板。

2.实现

#define MAX_N 200
//封装结构体
struct BigNum
    int len;//数的位数
    int a[MAX_N];//各数位数组

    BigNum(int x = 0)//通过初始化使其默认表示整形x = 0
        memset(a,0,sizeof(a));//初始化
        for (len = 0; x ;len++)
            a[len] = x % 10;
            x /= 10;
        
    
    int &operator [](int i)
        return a[i];
    

    void flatten(int L)//从第0位到第L-1位进行进位处理,L不小于有效长度
        len = L;
        for(int i = 0;i < len;i++)//从低位至高位依次处理进位
        a[i + 1] += a[i] / 10;
        a[i] %= 10;
        
        while (!a[len-1])
            len--;//去除多余0位,使其重置为有效长度
        
    

    void print()//输出
        for (int i = max(0,len-1);i>=0;i--)
            printf("%d",a[i]);
        
    
;

//重置+(两个大数相加)
BigNum operator+(BigNum a,BigNum b)
    BigNum c;
    int len = max(a.len,b.len);
    for (int i = 0; i<len ; i++ ) //从低位至高位依次作加法运算
        c[i]+= a[i]+b[i];
    
    c.flatten(len+1);
    return c;


//重置*(两个大数相乘)
BigNum operator*(BigNum a,BigNum b)
    BigNum c;
    int lenA = a.len,lenB = b.len;
    for (int i = 0; i < lenA; i++)//计算每一位的贡献
        for (int j = 0; j < lenB; j++)
            c[i+j] += a[i] * b[j];
        
    
    int max_len = lenA + lenB;
    c.flatten(max_len);
    return c;


//重置*(大数与int型相乘)
BigNum operator*(BigNum a,int b)
    BigNum c;
    int len = a.len;
    for (int i = 0; i < len; i++)//计算每一位的贡献
            c[i] = a[i] * b;
    
    c.flatten(len+11);//int型的最大长度为10位
    return c;

3.例题

例题传送门:洛谷P1009(阶乘之和)

1.题面

题目描述
用高精度计算出 S = 1 ! + 2 ! + 3 ! + ⋯ + n ! ( n ≤ 50 ) S = 1! + 2! + 3! + \\cdots + n!(n \\le 50) S=1!+2!+3!++n!n50

输入格式
一个正整数 n n n

输出格式
一个正整数 S S S,表示计算结果。

输入输出样例
输入 # 1 1 1
3 3 3
输出 # 1 1 1
9 9 9

说明/提示
【数据范围】
对于 100 % 100 \\% 100% 的数据, 1 ≤ n ≤ 50 。 1 \\le n \\le 50。 1n50

2.分析

阶乘达到22的时候,已经超出了long long的储存范围,这里利用前面写封装好的高精模板,就能轻松的解决此题。

3.AC代码

添加上相应的头文件后,就能正常运行啦!(这里只贴出main函数模拟题意代码)

int main()
    BigNum ans(0),fac(1);
    int n;
    cin>>n;
    for (int i = 1;i <= n;i++)
        fac = fac * i;
        ans = ans + fac;
    
    ans.print();
    return 0;


总结

到此为止,高精度运算就暂时告一段落了,高精度运算的主要实质为数组模拟大数进行逐位运算,理解起来不算非常复杂,而通过进一步封装,得到的高精度模板能够方便简洁地实现相应的大数运算。

以上是关于从零开始的算法学习生活——①高精度运算的主要内容,如果未能解决你的问题,请参考以下文章

从零开始的算法学习生活——①高精度运算

从零开始的算法学习生活——①高精度运算

转:从零开始学算法:高精度计算

用C++实现高精度加法运算

从零开始的python生活①手撕爬虫扒一扒力扣的用户刷题数据

高精度运算