高精度乘除法

高精度乘法

高精度问题在竞赛中很常见,也经常与其他类型的题目混用,是比较基础的算法,有固定的模板可以直接用我们先来看高精度乘法

高精度概念:数组 / 字符串 逐位存储超大数字,模拟手工竖式 乘法,分步计算进位,实现任意长度大整数相乘,不受数据类型位数限制。这样就可以避免使用long long类型时数据溢出的风险,而且时间复杂度仅为两个数的位数的乘积,这也可以有效减小时间复杂度避免超时。

高精度乘法的存储规则 :把数字倒着存入数组:个位存在下标 0,十位下标 1,百位下标 2以此类推

例:数字 123 → 数组 a0=3, a1=2, a2=1

这样存储的优势可以使相乘、进位时只向后扩展下标,不用频繁移位。

大概过程就是

设数字 A 长度n,数字 B 长度m,乘积最大长度一定是 n+m,提前开大小为n+m的结果数组res,初始全 0。

双重循环 :A 的第i位 × B 的第j位,结果加到 resi+j

原理 :第i位是(10i,第j位是10j,乘积权值是10i+j,对应resi+j

统一处理进位:遍历结果数组,每一位:

进位=resk/10,resk%10进位加到下一位resk+1.

去除前导零:结果数组高位多余 0 全部删掉,只剩数字。

我们来看一下具体的代码实现模板:

cpp 复制代码
string mul(string s1, string s2){
    int a[1005]={0},b[1005]={0},res[2010]={0};
    int n=s1.size(),m=s2.size();
    // 逆序存
    for(int i=0;i<n;i++) a[i]=s1[n-1-i]-'0';
    for(int i=0;i<m;i++) b[i]=s2[m-1-i]-'0';
    // 相乘累加
    for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
            res[i+j] += a[i]*b[j];
    // 处理进位
    for(int k=0;k<n+m;k++){
        res[k+1] += res[k]/10;
        res[k] %= 10;
    }
    // 去前导零
    int p=n+m-1;
    while(p>0 && res[p]==0) p--;
    string ans;
    for(int i=p;i>=0;i--) ans+=res[i]+'0';
    return ans;
}

运用这套模板可以直接解决大数相乘的问题,那我们在来看一道竞赛中高精度乘法的经典例题

这道题来自洛谷的P1045麦森数的计算、

题目链接:洛谷P1045

这道题刚开始看可能没有向高精度乘法的方面想但这就是高精度与快速幂的结合

本题使用高精度快速幂计算 2P,再减一得到麦森数2P-1,分三大模块:

计算总位数 :利用数学公式 Pxlog~10~2,无需高精度,直接算出数字总长度。

高精度快速幂求 2p

数组存储规则 :下标 1 代表个位,下标越大数字越高位,数组只保留后 500 位(题目仅要求末尾 500 位)。

rev:结果数组,初始值 1,代表 20;f:底数数组,初始值 2,代表底数 2。

cheng1():高精度大数乘法 rev = rev * f,快速幂中二进制位为 1 时执行。

cheng2():高精度大数平方 f = f * f,每次循环将底数平方。

快速幂二进制拆分 P,不断平方底数,遇二进制 1 则乘入结果。

处理 2p-1:直接个位数字减 1。

格式化输出:从最高位到个位倒序打印,每行输出 50 位,共 10 行,不足自动补 0。

我们看一下代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define endl '\n'
#define fi first
#define se second
const ll N=1e6+10;
// rev:存储最终结果2^p,f:快速幂底数2,sav:乘法临时缓存数组
int p,rev[1100],f[1100],sav[1100];

// 函数功能:rev = rev * f 高精度大数乘大数
void cheng1(){
	// 清空临时数组,防止上一轮计算残留数据
	memset(sav,0,sizeof(sav));
	// 遍历结果数组rev每一位,最多只计算后500位
	for(int i=1;i<=500;i++){
		// 遍历底数数组f每一位,最多只计算后500位
		for(int j=1;j<=500;j++){
			// i位对应10^(i-1),j位对应10^(j-1),乘积存到i+j-1位
			sav[i+j-1]+=rev[i]*f[j];
			// 取出当前位产生的进位,加到更高一位
			sav[i+j]+=sav[i+j-1]/10;
			// 当前位只保留个位数字
			sav[i+j-1]%=10;
		}
	}
	// 将临时数组sav的计算结果拷贝回结果数组rev
	memcpy(rev,sav,sizeof(rev));
}

// 函数功能:f = f * f 高精度大数平方
void cheng2(){
	// 清空临时数组,清除上一轮运算残留值
	memset(sav,0,sizeof(sav));
	// 遍历底数f的每一位,限制仅计算后500位
	for(int i=1;i<=500;i++){
		// 再次遍历底数f的每一位,完成平方运算
		for(int j=1;j<=500;j++){
			// 两位相乘,累加至对应权值位置
			sav[i+j-1]+=f[i]*f[j];
			// 本位进位传递到更高下标
			sav[i+j]+=sav[i+j-1]/10;
			// 本位保留个位数
			sav[i+j-1]%=10;
		}
	}
	// 把平方后的临时数组覆盖回底数数组f
	memcpy(f,sav,sizeof(f));
}

void solve()
{
    // 读入指数P
    cin>>p;
    // 利用对数公式计算2^p的十进制总位数并输出
    cout<<(int)(p*log10(2)+1);
    // 初始化结果数组rev,个位赋值1,代表初始值2^0=1
    rev[1]=1;
    // 初始化底数数组f,个位赋值2,代表底数2
    f[1]=2;
    // 快速幂二进制循环,拆分指数P
    while(p!=0){
    	// 如果当前二进制最低位为1,结果乘上当前底数
    	if(p%2==1){
    		cheng1();
    	}
    	// 指数右移一位,等价于除以2
    	p/=2;
    	// 底数平方,对应二进制高位进位
    	cheng2();
    }
    // 计算2^p - 1,直接个位数字减1
    rev[1]--;
    // 从最高位下标500倒序遍历到个位下标1,输出后500位
    for(int i=500;i>=1;i--){
    	// 每输出满50位就换行,满足题目每行50个数字要求
    	if(i%50==0) cout<<endl;
    	// 输出当前位数字
    	cout<<rev[i];
    }
}
signed main()
{
	IOS;
	ll T=1;
	while(T--)
	solve();
	return 0;
}

高精度除法

首先我们来看高精度除以普通数

原理

从最高位往低位依次运算:

余数 rem = 0

每一位:rem = rem * 10 + 当前位数字

当前商 = rem / 除数,新余数 = rem % 除数

最后去掉商的前导零,rem 为最终余数

模板:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;

// a:被除数字符串,b:除数(普通整数),返回 pair<商,余数>
pair<string, int> div_small(string a, int b)
{
    vector<int> res; // 存商(正序)
    int rem = 0;     // 余数
    // 从最高位遍历到最低位
    for (int i = 0; i < a.size(); i++)
    {
        // 上一轮余数*10 + 当前数字
        rem = rem * 10 + (a[i] - '0');
        // 当前位商
        res.push_back(rem / b);
        // 更新余数
        rem = rem % b;
    }
    // 去除前导0
    int idx = 0;
    while (idx < res.size() && res[idx] == 0) idx++;
    string ans;
    for (; idx < res.size(); idx++) ans += (char)(res[idx] + '0');
    // 特判商为0的情况
    if (ans.empty()) ans = "0";
    return {ans, rem};
}

int main()
{
    string s;
    int x;
    cin >> s >> x;
    auto [shang, yu] = div_small(s, x);
    cout << "商:" << shang << "\n余数:" << yu;
    return 0;
}

这个是比较简单的没有很复杂的运算过程但高精度除以高精度就比较复杂

具体思路就是

1.先比较两数长度:被除数 < 除数,商就是 0,余数就是被除数

2.对齐位数,二分试商:在 0~9 之间找最大 k,使得 除数 × k ≤ 当前截取的被除数片段

3.每次用高精度减法,被除数片段减去 除数k,保存 k 作为当前位商

4.最后去除商前导零

我们看一下模板:

cpp 复制代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

// 1. 高精度比较函数
// a > b 返回 1;a == b 返回 0;a < b 返回 -1
int cmp(string a, string b) {
    // 长度不同直接比长度
    if (a.size() != b.size()) {
        return a.size() > b.size() ? 1 : -1;
    }
    // 长度相同逐位对比
    for (int i = 0; i < a.size(); i++) {
        if (a[i] > b[i]) return 1;
        if (a[i] < b[i]) return -1;
    }
    return 0;
}

// 2. 高精度减法 a - b,保证 a >= b,返回结果字符串
string sub(string a, string b) {
    vector<int> x, y, res;
    // 转数字数组,正序
    for (char c : a) x.push_back(c - '0');
    for (char c : b) y.push_back(c - '0');
    // 补前导0对齐长度
    while (y.size() < x.size()) y.insert(y.begin(), 0);

    int borrow = 0; // 借位
    // 从最低位倒着减
    for (int i = x.size() - 1; i >= 0; i--) {
        int now = x[i] - borrow - y[i];
        borrow = 0;
        if (now < 0) {
            now += 10;
            borrow = 1;
        }
        res.push_back(now);
    }
    reverse(res.begin(), res.end());
    // 去除前导零
    int idx = 0;
    while (idx < res.size() && res[idx] == 0) idx++;
    string ans;
    for (; idx < res.size(); idx++) ans += res[idx] + '0';
    return ans.empty() ? "0" : ans;
}

// 3. 高精度大数 × 单个0~9整数
string mul_small(string a, int k) {
    if (k == 0) return "0";
    vector<int> num;
    for (char c : a) num.push_back(c - '0');
    reverse(num.begin(), num.end()); // 逆序方便进位
    int carry = 0;
    for (int i = 0; i < num.size(); i++) {
        int val = num[i] * k + carry;
        num[i] = val % 10;
        carry = val / 10;
    }
    while (carry) {
        num.push_back(carry % 10);
        carry /= 10;
    }
    reverse(num.begin(), num.end());
    string ans;
    for (int d : num) ans += d + '0';
    return ans;
}

// 4. 高精度 ÷ 高精度主函数
// 返回 pair<商, 余数>
pair<string, string> div_big(string A, string B) {
    string shang = ""; // 存储商
    string yu = "0";   // 存储当前余数片段

    // 遍历被除数每一位,从高位到低位
    for (char c : A) {
        // 把当前数字拼到余数末尾
        yu += c;
        // 去掉余数前导0
        int st = 0;
        while (st < yu.size() && yu[st] == '0') st++;
        if (st == yu.size()) yu = "0";
        else yu = yu.substr(st);

        // 二分试商:0~9找最大k 满足 B*k <= yu
        int l = 0, r = 9, best = 0;
        while (l <= r) {
            int mid = (l + r) / 2;
            string temp = mul_small(B, mid);
            if (cmp(temp, yu) <= 0) {
                best = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        // 当前位商存入结果
        shang += (char)('0' + best);
        // 余数 = 余数 - B * best
        string sub_val = mul_small(B, best);
        yu = sub(yu, sub_val);
    }

    // 清除商的前导零
    int pos = 0;
    while (pos < shang.size() && shang[pos] == '0') pos++;
    if (pos == shang.size()) shang = "0";
    else shang = shang.substr(pos);

    return {shang, yu};
}

// 测试主函数
int main() {
    string a, b;
    cin >> a >> b;
    auto ans = div_big(a, b);
    cout << "商:" << ans.first << endl;
    cout << "余数:" << ans.second << endl;
    return 0;
}
相关推荐
我找到地球的支点啦17 分钟前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
罗西的思考30 分钟前
【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
人工智能·算法·机器学习
浪里镖客1 小时前
位姿转换矩阵写法-个人习惯(计算机理解其实是相反的)
线性代数·算法·矩阵
小白羊丨4 小时前
如何诊断 Prompt 模板导致的效果下降?
人工智能·算法·prompt
OPEN-F5 小时前
C++11/14新特性精讲:移动语义与智能指针实战
开发语言·c++·算法
lisin-lee-cooper6 小时前
【leetcode658】有序数组找出k个最接近x的数
java·数据结构·算法
sunburn-6 小时前
Java堆(Heap)详解与实战教学
java·开发语言·数据结构·ide·算法
智碳能碳管理平台7 小时前
工业能耗台账标准化:能碳管理系统的数据口径怎么设计
算法·能碳管理系统·智碳能碳管理平台·企业能碳管理系统·碳排放核算软件·绿色工厂申报saas·能碳管理平台
带多刺的玫瑰7 小时前
Leecode#15刷题之三数之和
算法·leetcode·职场和发展
哭泣方源炼蛊7 小时前
并查集进阶 P1(带权并查集,并查集分类)
数据结构·c++·算法·二进制·带权并查集