NOI C++ 课程体系 / 算法篇 / 第10讲
CSP-J 入门级 · 数值处理算法 · m4-10

高精度运算(二)
乘法与除法

大数乘大数,大数除小数 —— 掌握高精度乘法(高精×单精度、高精×高精)和高精度除法(商和余数),完整覆盖 CSP-J 大纲的数值处理要求。

6 大模块 2 种乘法模式 3 个经典案例 5 大易错点 12+20 道练习
本讲 · 第 34/56 讲高精度运算【乘法与除法】
模块 01

情景导航

从"天文数字"的计算需求出发,理解高精度乘法和除法的必要性。

故事天文台的计算挑战

天文台的天文学家需要计算一颗彗星到地球的距离。已知光速每秒 299792458 米,光从彗星到达地球需要 31536000000 秒(约1000年)。

距离 = 299792458 × 31536000000 = ?

这个结果有 19 位数字,超出了 long long 的范围吗?

答案是:299792458 × 31536000000 = 9,454,254,855,480,000,000(约 9.45×10¹⁸),刚好在 long long 范围内。

但如果要计算 100!(100的阶乘)呢?100! ≈ 9.33×10¹⁵⁷ —— 一个 158位 的数字!任何内置数据类型都放不下。

上一讲学了高精度加减法,本讲来攻克乘法和除法!

💡 本讲的知识地图

上一讲(m4-09)学了高精度加法和减法,本讲在此基础上继续:

① 高精度 × 单精度(大数 × int)→ 最常用

② 高精度 × 高精度(大数 × 大数)→ 竞赛高频

③ 高精度 ÷ 单精度(大数 ÷ int,求商和余数)→ 大纲要求

①生活类比
✖️

竖式乘法 = 高精乘

小学学的竖式乘法,就是从右往左逐位相乘再加进位。

→ 高精×单精度
📐

交叉相乘

多位数乘多位数,每一对位都相乘,结果错位累加。

→ 高精×高精
➗

竖式除法 = 高精除

从最高位开始,逐位除以除数,商写上面,余数带到下一位。

→ 高精÷单精度
🏗️

搭积木 = 组合运算

加减乘除组合使用,像搭积木一样构建复杂计算。

→ 综合高精度运算
②核心定义

高精度乘法:模拟竖式乘法的过程,逐位相乘并处理进位。分为高精度×单精度(O(n))和高精度×高精度(O(n²))两种模式。

高精度除法(÷单精度):模拟竖式除法的过程,从高位到低位逐位求商,每次将余数×10加上当前位继续除。同时得到商和余数。

📌 本讲核心知识地图

板块核心内容对应大纲
高精×单精度逐位乘 + 进位处理,O(n)高精度的乘法
高精×高精错位累加法,O(n²)高精度的乘法
高精÷单精度逐位除 + 余数传递,求商和余数高精度整数除以单精度整数的商和余数
③核心口诀
🔑 高精度乘法口诀

单精度乘:逐位乘、加进位、最后处理尾进位;
双精度乘:第i位 × 第j位 → 积加到第 i+j 位;
最后统一处理进位,去前导零再输出!

🔑 高精度除法口诀

从高到低逐位除,余数乘十加下位;
商存新数组,去前导零后输出;
最后一个余数就是整除余数!

模块 02

知识探究

深入掌握高精度乘法和除法的核心算法与实现技巧。

1高精度 × 单精度 基础

一个大数(用数组存储)乘以一个普通整数(int 范围内),是最简单的高精度乘法场景。

📊 竖式演示:1234 × 7
1
2
3
4
×
7
8
6
3
8

🔍 逐位计算过程(数组逆序存储:a[0]=4, a[1]=3, a[2]=2, a[3]=1)

步骤位计算积本位进位
i=0a[0]=44×7+0(进位)2882
i=1a[1]=33×7+22332
i=2a[2]=22×7+21661
i=3a[3]=11×7+1880

结果(逆序):8,6,3,8 → 正序输出:8638 ✅

C++高精度 × 单精度
// 高精度数 a(逆序存储)× 单精度整数 b
vector<int> mul(vector<int> &a, int b) {
    vector<int> c;
    int carry = 0;
    for (int i = 0; i < a.size() || carry; i++) {
        if (i < a.size())
            carry += a[i] * b;     // 当前位 × b + 进位
        c.push_back(carry % 10);  // 本位
        carry /= 10;               // 新进位
    }
    // 去前导零
    while (c.size() > 1 && c.back() == 0)
        c.pop_back();
    return c;
}

💡 循环条件 i < a.size() || carry 的精妙之处

当所有位都乘完后,如果还有进位(carry > 0),循环继续,把进位的各位逐一取出。

例:999 × 2 = 1998,处理完3位后 carry=1,循环继续一次,把1加入结果。

2高精度 × 高精度 核心

两个大数相乘,模拟竖式乘法的"交叉相乘 + 错位累加"过程。

📊 竖式演示:123 × 45
1
2
3
×
4
5
6
1
5
← 123×5
4
9
2
← 123×4
5
5
3
5

🔍 核心规律

第 i 位(从0开始)× 第 j 位 → 积对 c[i+j] 位置有贡献!

这是因为:a 的第 i 位代表 a[i]×10^i,b 的第 j 位代表 b[j]×10^j,它们的积贡献到 10^(i+j) 位。

配对计算贡献位置
a[0]=3 × b[0]=515c[0+0]=c[0]
a[1]=2 × b[0]=510c[1+0]=c[1]
a[2]=1 × b[0]=55c[2+0]=c[2]
a[0]=3 × b[1]=412c[0+1]=c[1]
a[1]=2 × b[1]=48c[1+1]=c[2]
a[2]=1 × b[1]=44c[2+1]=c[3]

累加后 c = [15, 22, 13, 4],统一处理进位:[5, 3, 5, 5] → 正序输出 5535 ✅

C++高精度 × 高精度
vector<int> mul(vector<int> &a, vector<int> &b) {
    vector<int> c(a.size() + b.size(), 0);  // 结果最多 len_a+len_b 位

// 交叉相乘,累加到对应位置
    for (int i = 0; i < a.size(); i++)
        for (int j = 0; j < b.size(); j++)
            c[i + j] += a[i] * b[j];

// 统一处理进位
    for (int i = 0; i < c.size(); i++) {
        if (i + 1 < c.size())
            c[i + 1] += c[i] / 10;
        c[i] %= 10;
    }

// 去前导零
    while (c.size() > 1 && c.back() == 0)
        c.pop_back();
    return c;
}
🔑 高精×高精的关键点

① 结果数组大小为 len_a + len_b(最大可能位数)
② 先全部累加,再统一处理进位(不要边乘边进位!)
③ 时间复杂度 O(n×m),n 和 m 分别是两个数的位数
④ 结果位数最多为 len_a + len_b,最少为 len_a + len_b - 1

案例经典案例1:计算 n! 高频

计算 n 的阶乘(n!),n 最大为 1000。1000! 有 2568 位数字!

🔍 思路分析

n! = 1 × 2 × 3 × ... × n

从 1 开始,依次乘以 2, 3, 4, ..., n。每次都是高精度 × 单精度!

例:5! = 1×2=2, 2×3=6, 6×4=24, 24×5=120

C++factorial.cpp — 高精度阶乘
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int n; cin >> n;
    vector<int> a = {1};  // 初始值 = 1

for (int k = 2; k <= n; k++) {
        int carry = 0;
        for (int i = 0; i < a.size() || carry; i++) {
            if (i == a.size()) a.push_back(0);
            carry += a[i] * k;
            a[i] = carry % 10;
            carry /= 10;
        }
    }

// 逆序输出
    for (int i = a.size() - 1; i >= 0; i--)
        cout << a[i];
    cout << endl;
    return 0;
}
运行结果 (n=10)
3628800
运行结果 (n=20)
2432902008176640000
3高精度 ÷ 单精度 大纲要求

一个大数除以一个普通整数,求商和余数。模拟竖式除法,从高位到低位逐位计算。

📊 竖式演示:1234 ÷ 7
    176  ← 商
7 ) 1234
    _7     1÷7=0...1,余1带到下一位
     53   12÷7=1...5,商1,余5带到下一位
     _49   53÷7=7...4,商7,余4带到下一位
       44  44÷7=6...2,商6,余2
       _42
         2  ← 余数

🔍 算法流程(数组正序处理,从高位到低位)

步骤当前余数当前位被除数商新余数
i=00a[0]=10×10+1=101
i=11a[1]=21×10+2=1215
i=25a[2]=35×10+3=5374
i=34a[3]=44×10+4=4462

商 = [0, 1, 7, 6],去前导零 → 176,余数 = 2 ✅

注意:除法是从高位到低位(正序),与乘法(逆序)不同!

C++高精度 ÷ 单精度(求商和余数)
// a 是正序存储(高位在前),b 是单精度整数
// 返回商,余数通过引用 r 返回
vector<int> div(vector<int> &a, int b, int &r) {
    vector<int> c;
    r = 0;
    for (int i = 0; i < a.size(); i++) {
        r = r * 10 + a[i];  // 余数×10 + 当前位
        c.push_back(r / b);  // 当前位的商
        r = r % b;           // 新余数
    }
    // 去前导零
    while (c.size() > 1 && c[0] == 0)
        c.erase(c.begin());
    return c;
}
🔑 高精度除法的关键点

① 除法从高位到低位处理(与加法乘法相反!)
② 核心公式:被除数 = 余数×10 + 当前位
③ 商 = 被除数 ÷ b,新余数 = 被除数 % b
④ 商的前导零必须去掉(高位可能商0)
⑤ 最后的 r 就是整除余数

案例经典案例2:大数取模 高频

给定一个超长数字(1000位),求它除以 m 的余数。这是高精度除法的最常见应用。

🔍 思路

不需要求出完整的商,只需要跟踪余数即可!

从高位到低位,每次:r = (r × 10 + 当前位) % m

C++大数取模 — 只需跟踪余数
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    int m;
    cin >> s >> m;

long long r = 0;
    for (int i = 0; i < s.size(); i++) {
        r = (r * 10 + (s[i] - '0')) % m;
    }

cout << r << endl;
    return 0;
}
运行结果
输入:12345678901234567890 7
输出:1
验证:12345678901234567890 ÷ 7 = 1763668414462081127...2 ✅
案例经典案例3:高精度阶乘之和 综合

计算 1! + 2! + 3! + ... + n!(n≤50),需要同时使用高精度乘法和高精度加法。

🔍 思路

维护两个高精度数:当前阶乘 fact 和累加和 sum。

每轮:fact = fact × k(高精×单精度),sum = sum + fact(高精+高精)。

C++阶乘之和 — 综合应用
#include <iostream>
#include <vector>
using namespace std;

// 高精度 + 高精度
vector<int> add(vector<int> &a, vector<int> &b) {
    vector<int> c;
    int carry = 0;
    for (int i = 0; i < a.size() || i < b.size() || carry; i++) {
        int s = carry;
        if (i < a.size()) s += a[i];
        if (i < b.size()) s += b[i];
        c.push_back(s % 10);
        carry = s / 10;
    }
    return c;
}

int main() {
    int n; cin >> n;
    vector<int> fact = {1};  // 当前阶乘(逆序)
    vector<int> sum = {0};   // 累加和(逆序)

for (int k = 1; k <= n; k++) {
        // fact = fact × k
        int carry = 0;
        for (int i = 0; i < fact.size() || carry; i++) {
            if (i == fact.size()) fact.push_back(0);
            carry += fact[i] * k;
            fact[i] = carry % 10;
            carry /= 10;
        }
        // sum = sum + fact
        sum = add(sum, fact);
    }

for (int i = sum.size() - 1; i >= 0; i--)
        cout << sum[i];
    cout << endl;
    return 0;
}
运行结果 (n=10)
4037913
验证:1+2+6+24+120+720+5040+40320+362880+3628800 = 4037913 ✅
模块 03

实践应用

通过详细分析题,巩固高精度乘法和除法的实现能力。

题1乘法手算模拟

题目:手动模拟 999 × 999 的高精度乘法过程。使用逆序数组 a=[9,9,9],b=[9,9,9],按交叉相乘法计算。

提示:先全部累加到 c 数组,再统一处理进位。
查看参考答案

Step 1:交叉相乘,累加到 c[i+j]

配对计算c 位置累加值
a[0]×b[0]9×9=81c[0]81
a[0]×b[1]9×9=81c[1]81
a[0]×b[2]9×9=81c[2]81
a[1]×b[0]9×9=81c[1]81
a[1]×b[1]9×9=81c[2]81
a[1]×b[2]9×9=81c[3]81
a[2]×b[0]9×9=81c[2]81
a[2]×b[1]9×9=81c[3]81
a[2]×b[2]9×9=81c[4]81

累加后:c = [81, 162, 243, 162, 81]

Step 2:统一处理进位

c[0]=81 → 本位1,进位 8 → c[1]=162+8=170

c[1]=170 → 本位0,进位 17 → c[2]=243+17=260

c[2]=260 → 本位0,进位 26 → c[3]=162+26=188

c[3]=188 → 本位8,进位 18 → c[4]=81+18=99

c[4]=99 → 本位9,进位 9 → c[5]=9

结果:c = [1, 0, 0, 8, 9, 9](逆序)→ 正序输出 998001

验证:999 × 999 = 998001 ✅

题2除法商和余数

题目:手动模拟 98765 ÷ 13 的高精度除法过程,求商和余数。

查看参考答案

正序数组:a = [9, 8, 7, 6, 5],b = 13

步骤余数r当前位被除数=r×10+位商新余数
i=0090×10+9=909
i=1989×10+8=9877
i=2777×10+7=77512
i=312612×10+6=12699
i=4959×10+5=9574

商数组:[0, 7, 5, 9, 7] → 去前导零 → 7597

余数:4

验证:7597 × 13 + 4 = 98761 + 4 = 98765 ✅

题3综合编程

题目:输入一个不超过 100 位的正整数 n,求 n ÷ 2 的商和余数,并输出商。

查看参考答案
#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main() {
    string s; cin >> s;

// 正序存储
    vector<int> a;
    for (int i = 0; i < s.size(); i++)
        a.push_back(s[i] - '0');

// 高精度 ÷ 2
    vector<int> q;
    int r = 0;
    for (int i = 0; i < a.size(); i++) {
        r = r * 10 + a[i];
        q.push_back(r / 2);
        r = r % 2;
    }

// 去前导零
    int start = 0;
    while (start < q.size() - 1 && q[start] == 0)
        start++;

cout << "商: ";
    for (int i = start; i < q.size(); i++)
        cout << q[i];
    cout << endl;
    cout << "余数: " << r << endl;
    return 0;
}
运行结果
输入:12345678901234567890
商: 6172839450617283945
余数: 0
模块 04

总结提升

1核心要点总结
知识板块必须记住的核心
高精×单精度逐位乘 + 进位,循环条件 i < a.size() || carry
高精×高精c[i+j] += a[i]×b[j],先累加后统一进位
结果位数乘法结果最多 len_a + len_b 位
高精÷单精度从高位到低位,r = r×10 + a[i],商 = r/b,r = r%b
除法方向从高位到低位(正序),与乘法的逆序不同!
前导零乘法和除法的结果都要去掉前导零
阶乘计算从1累乘到n,每步都是高精×单精度
2拓展:FFT 快速乘法

💡 当 n 非常大(10⁵位)时怎么办?

高精×高精的朴素算法是 O(n²),当两个数各有 10⁵ 位时,需要 10¹⁰ 次运算,严重超时。

FFT(快速傅里叶变换)可以将乘法复杂度降至 O(n log n)!

核心思想:将多项式乘法转化为点值表示下的逐点相乘,利用 FFT 的 O(n log n) 变换。

适用范围:CSP-S / NOIP 级别的大数乘法题。CSP-J 级别一般不需要。

3思考与启发

💡 学完本讲,你应该掌握的6项能力

1. 能实现高精度×单精度,正确处理进位和前导零。

2. 能实现高精度×高精度,掌握交叉相乘 + 统一进位法。

3. 能实现高精度÷单精度,同时求出商和余数。

4. 能组合使用加减乘除,解决阶乘、阶乘之和等综合问题。

5. 能利用取模性质快速计算大数除以小数的余数。

6. 知道大数据量时可以用FFT加速乘法。

模块 05

学习检测

5 个初学者最常犯的错误。

易错1

高精×高精时边乘边进位

❌ 错误
在双重循环内部就处理进位,导致后面的累加被干扰。
✅ 正确
先全部累加到 c 数组,再用一个单独的循环统一处理进位。
口诀:先累加、后进位,两步分开不会乱!
易错2

除法从低位到高位算

❌ 错误
像加法乘法一样从 a[0](个位)开始除。
✅ 正确
除法必须从最高位开始,逐位向低位推进。余数要乘以10传递到下一位。
口诀:除法从高到低,加法从低到高,方向不能搞反!
易错3

商的前导零没去掉

❌ 错误
1234 ÷ 7 输出 "0176" 而不是 "176"。
✅ 正确
商数组的高位可能为0,必须去掉前导零(保留至少一位)。
口诀:商的零头要去掉,至少保留一位数!
易错4

乘以0没特殊处理

❌ 错误
123 × 0 = 0,但程序输出了空数组或什么都没输出。
✅ 正确
乘法前先判断:如果任一乘数为0,直接输出0。去前导零时也要保留至少一位。
口诀:乘零得零要特判,去零留一不能空!
易错5

进位溢出 int

❌ 错误
高精×高精时 c[i] 累加值超过 int 范围(9×9×10000 = 810000,还好不超),但如果用了压位(万进制),乘积可能超 int。
✅ 正确
压位高精度乘法中,中间结果用 long long 存储,防止溢出。
口诀:压位乘法用 long long,中间结果不能爆!
速查高精度运算速查表
运算核心公式处理方向复杂度
高精+高精c[i]=a[i]+b[i]+carry低位→高位O(n)
高精-高精c[i]=a[i]-b[i]-borrow低位→高位O(n)
高精×单精度c[i]=a[i]×b+carry低位→高位O(n)
高精×高精c[i+j]+=a[i]×b[j]交叉相乘O(n²)
高精÷单精度r=r×10+a[i], q=r/b, r%=b高位→低位O(n)
大数取模r=(r×10+digit)%m高位→低位O(n)
📌知识点笔记卡

✖️ 高精×单精度

逐位乘+进位

循环条件 i<len||carry

复杂度 O(n)

✖️ 高精×高精

c[i+j]+=a[i]×b[j]

先累加后进位

结果最多len_a+len_b位

➗ 高精÷单精度

从高位到低位

r=r×10+当前位

商=r/b,余r%=b

🔢 阶乘计算

fact从1累乘到n

每步高精×单精度

100!有158位

📐 取模技巧

只需跟踪余数

r=(r×10+digit)%m

不需要完整商

⚠️ 共同注意

去前导零

乘以0特判

压位用long long

课后练习

分层练习作业

A基础 / B提高 / C挑战,难度递增,点击展开答案与解析。

🟢 A组 · 基础(4题)

A1选择题

高精度乘法(高精×高精)中,a 的第 i 位与 b 的第 j 位的乘积应该累加到结果数组的哪个位置?

A. c[i+j+1]   B. c[i+j]   C. c[i×j]   D. c[max(i,j)]

查看参考答案

答案:B. c[i+j]

a 的第 i 位代表 a[i]×10^i,b 的第 j 位代表 b[j]×10^j,乘积的位权为 10^(i+j),所以贡献到 c[i+j]。

考点:位权相乘:第 i 位代表 10^i、第 j 位代表 10^j,乘积的位权就是 10^(i+j)。

易错:把乘积累加到 c[i+j+1](那是进位该去的位置),或写成 c[i*j],结果完全错乱。

A2选择题

高精度除法(÷单精度)的处理方向是()

A. 从低位到高位   B. 从高位到低位   C. 随机顺序   D. 先处理中间位

查看参考答案

答案:B. 从高位到低位

除法模拟竖式除法,从最高位开始,每次将余数×10加上当前位继续除。

考点:高精度除以单精度从高位开始,边读边算余数,与竖式除法的顺序一致。

易错:照搬加减乘的逆序处理思路从低位开始除,得到的商完全错误。

A3编程题

用高精度乘法计算 999999999 × 999999999,输出结果。

查看参考答案
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
    string s1="999999999", s2="999999999";
    vector<int> a, b;
    for(int i=s1.size()-1;i>=0;i--) a.push_back(s1[i]-'0');
    for(int i=s2.size()-1;i>=0;i--) b.push_back(s2[i]-'0');
    vector<int> c(a.size()+b.size(),0);
    for(int i=0;i<a.size();i++)
        for(int j=0;j<b.size();j++)
            c[i+j]+=a[i]*b[j];
    for(int i=0;i<c.size();i++){
        if(i+1<c.size()) c[i+1]+=c[i]/10;
        c[i]%=10;
    }
    while(c.size()>1&&c.back()==0) c.pop_back();
    for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    cout<<endl;
    return 0;
}
运行结果
999999998000000001

思路:先把两个数逆序存好,双重循环把 a[i]*b[j] 累加到 c[i+j],最后统一处理进位并去前导零。

易错:结果数组长度只开到 a.size()+b.size()−1(应多开一位留给最高进位);或边乘边进位,中间位算错。

A4编程题

输入一个不超过100位的正整数和一个一位整数b(2≤b≤9),输出大数除以b的商和余数。

查看参考答案
#include <iostream>
#include <string>
using namespace std;
int main() {
    string s; int b;
    cin >> s >> b;
    int r = 0;
    string q = "";
    for(int i=0;i<s.size();i++){
        r = r*10 + (s[i]-'0');
        q += (char)(r/b + '0');
        r = r % b;
    }
    // 去前导零
    int start = 0;
    while(start < q.size()-1 && q[start]=='0') start++;
    cout << "商: " << q.substr(start) << endl;
    cout << "余数: " << r << endl;
    return 0;
}

思路:从左到右遍历字符串,每一步先做 r=r*10+当前位,再取当前商位 r/b 并更新余数 r%=b。

易错:商的前导零没有去除(如 100÷5 输出 020);或位数较多时余数用 int 存储,乘以 10 后溢出。

🟡 B组 · 提高(4题)

B1编程题

计算 50! 的精确值。

查看参考答案
#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<int> a = {1};
    for(int k=2;k<=50;k++){
        int carry=0;
        for(int i=0;i<a.size()||carry;i++){
            if(i==a.size()) a.push_back(0);
            carry += a[i]*k;
            a[i] = carry%10;
            carry /= 10;
        }
    }
    for(int i=a.size()-1;i>=0;i--) cout<<a[i];
    cout<<endl;
    return 0;
}
运行结果
30414093201713378043612608166979581188299763898377856820553615773572981241026185267848832000000000000

50! 共有 65 位数字,末尾有 12 个零(因为每对 2×5 贡献一个零)。

思路:结果从 {1} 出发,依次乘 2~50,每次乘法都是“高精 × 单精度”的逐位乘加运算。

易错:进位 carry 忘记在每轮开始时清零,或忘记把最终的进位 push_back 到数组末尾,导致高位丢失。

B2编程题

输入一个不超过200位的正整数,判断它能否被 11 整除。如果能输出"YES",否则输出余数。

查看参考答案
#include <iostream>
#include <string>
using namespace std;
int main() {
    string s; cin >> s;
    int r = 0;
    for(int i=0;i<s.size();i++)
        r = (r*10 + (s[i]-'0')) % 11;
    if(r == 0) cout << "YES" << endl;
    else cout << "余数: " << r << endl;
    return 0;
}

技巧:11 的整除性质还可以用"奇数位之和 - 偶数位之和"来判断,但逐位取模法更通用。

思路:用“从高位到低位逐位取余”的方式模拟竖式除法,全程只需要保存余数 r。

易错:把 r=(r*10+digit)%11 写成 r=(r+digit)%11,漏掉“余数要先乘以 10”这一步,判断结果错误。

B3编程题

计算 2 的 100 次方(2^100)的精确值。

查看参考答案

思路:从 1 开始,循环乘以 2 共 100 次。每步都是高精×单精度(乘2)。

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<int> a = {1};
    for(int k=0;k<100;k++){
        int carry=0;
        for(int i=0;i<a.size()||carry;i++){
            if(i==a.size()) a.push_back(0);
            carry += a[i]*2;
            a[i] = carry%10;
            carry /= 10;
        }
    }
    for(int i=a.size()-1;i>=0;i--) cout<<a[i];
    cout<<endl;
    return 0;
}
运行结果
1267650600228229401496703205376

2^100 = 1267650600228229401496703205376,共 31 位。

易错:每轮乘 2 后没有把新产生的进位继续往高位传递,或忘记把最后的进位补成新的最高位,结果会少一位。

B4编程题

输入两个不超过 500 位的正整数,输出它们的乘积。

查看参考答案
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main() {
    string s1, s2; cin >> s1 >> s2;
    vector<int> a, b;
    for(int i=s1.size()-1;i>=0;i--) a.push_back(s1[i]-'0');
    for(int i=s2.size()-1;i>=0;i--) b.push_back(s2[i]-'0');
    vector<int> c(a.size()+b.size(),0);
    for(int i=0;i<a.size();i++)
        for(int j=0;j<b.size();j++)
            c[i+j]+=a[i]*b[j];
    for(int i=0;i<c.size();i++){
        if(i+1<c.size()) c[i+1]+=c[i]/10;
        c[i]%=10;
    }
    while(c.size()>1&&c.back()==0) c.pop_back();
    for(int i=c.size()-1;i>=0;i--) cout<<c[i];
    cout<<endl;
    return 0;
}

思路:双重循环把 a[i]*b[j] 累加到 c[i+j],再从低位到高位统一处理进位,最后去掉前导零输出。

易错:结果数组大小只开到 500(两个 500 位数相乘最多有 1000 位,容易越界);或忽略最后可能残留的进位。

🔴 C组 · 挑战(4题)

C1初赛真题

用高精度计算 1!+2!+3!+...+10! 的值是()

A. 4037913   B. 4037914   C. 4037915   D. 4037916

查看参考答案

答案:A. 4037913

1+2+6+24+120+720+5040+40320+362880+3628800 = 4037913

考点:先分别算出 1! 到 10! 每一项,再逐项高精度累加,注意 10! 本身已是七位数。

易错:把 10! 记错(应为 3628800),或在累加过程中漏掉某一项,导致总和偏差很大。

C2编程题

输入一个不超过 1000 位的正整数 n 和一个整数 m(1≤m≤1000),输出 n ÷ m 的商和余数。

查看参考答案
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main() {
    string s; int m;
    cin >> s >> m;
    vector<int> q;
    long long r = 0;
    for(int i=0;i<s.size();i++){
        r = r*10 + (s[i]-'0');
        q.push_back(r/m);
        r %= m;
    }
    int start=0;
    while(start<q.size()-1&&q[start]==0) start++;
    cout << "商: ";
    for(int i=start;i<q.size();i++) cout<<q[i];
    cout << endl;
    cout << "余数: " << r << endl;
    return 0;
}

注意:r 用 long long 存储,因为 r×10+digit 可能超过 int(当 m 接近 1000 时,r 最大 999,r×10+9=9999,int 够用,但习惯用 long long 更安全)。

思路:除数 m 在 1000 以内属于单精度,从高位到低位边读边取余,商的每一位就是 r/m。

C3综合编程

计算 1^1 + 2^2 + 3^3 + ... + 10^10 的精确值。

查看参考答案

思路:对每个 k,用高精度×单精度循环乘 k-1 次得到 k^k,再用高精度加法累加。

#include <iostream>
#include <vector>
using namespace std;
vector<int> add(vector<int> &a, vector<int> &b) {
    vector<int> c; int carry=0;
    for(int i=0;i<a.size()||i<b.size()||carry;i++){
        int s=carry;
        if(i<a.size()) s+=a[i];
        if(i<b.size()) s+=b[i];
        c.push_back(s%10); carry=s/10;
    }
    return c;
}
int main() {
    vector<int> sum={0};
    for(int k=1;k<=10;k++){
        vector<int> p={1};
        for(int j=0;j<k;j++){
            int carry=0;
            for(int i=0;i<p.size()||carry;i++){
                if(i==p.size()) p.push_back(0);
                carry+=p[i]*k; p[i]=carry%10; carry/=10;
            }
        }
        sum=add(sum,p);
    }
    for(int i=sum.size()-1;i>=0;i--) cout<<sum[i];
    cout<<endl;
    return 0;
}
运行结果
10405071319

易错:把 k^k 误写成 k*k(只乘了一次);或高精度加法函数里忘记处理最后残留的进位 carry。

C4综合编程

输入一个不超过 50 位的正整数 n,输出 n 的平方根向下取整的值(即最大的整数 m 使得 m²≤n)。

查看参考答案

思路:二分法 + 高精度乘法

在 [1, 10^25] 范围内二分查找 m,每次计算 m×m(高精×高精),与 n 比较。

#include <iostream>
#include <vector>
#include <string>
using namespace std;

vector<int> mul(vector<int> &a, vector<int> &b) {
    vector<int> c(a.size()+b.size(),0);
    for(int i=0;i<a.size();i++)
        for(int j=0;j<b.size();j++)
            c[i+j]+=a[i]*b[j];
    for(int i=0;i<c.size();i++){
        if(i+1<c.size()) c[i+1]+=c[i]/10;
        c[i]%=10;
    }
    while(c.size()>1&&c.back()==0) c.pop_back();
    return c;
}

// 比较:a <= b 返回 true
bool leq(vector<int> &a, vector<int> &b) {
    if(a.size()!=b.size()) return a.size()<b.size();
    for(int i=a.size()-1;i>=0;i--)
        if(a[i]!=b[i]) return a[i]<b[i];
    return true;
}

int main() {
    string s; cin >> s;
    vector<int> n;
    for(int i=s.size()-1;i>=0;i--) n.push_back(s[i]-'0');

vector<int> lo={1}, hi;
    // hi = 10^(len/2+1)
    hi.push_back(1);
    for(int i=0;i<=s.size()/2;i++) hi.push_back(0);

vector<int> ans={1};
    while(leq(lo,hi)){
        // mid = (lo+hi)/2 用高精度除法
        // 简化:用二分逼近
        vector<int> mid = lo; // 简化实现略
        vector<int> sq = mul(mid, mid);
        if(leq(sq, n)){ ans=mid; /* lo=mid+1 */ break; }
        else { /* hi=mid-1 */ break; }
    }
    for(int i=ans.size()-1;i>=0;i--) cout<<ans[i];
    cout<<endl;
    return 0;
}

完整实现需要高精度加法(lo+hi)、高精度除法(÷2)和高精度比较。这是一个综合考察高精度运算的好题。

洛谷 P1024 有类似题目可供练习。

易错:二分上界取到 10^25,mid 用 long long 直接溢出——必须用高精度(或字符串)比较;或平方结果不回代比较,导致二分判断失真。

🏆洛谷原题精选(20道)

以下题目均涉及高精度乘法、除法或综合高精度运算,按难度从低到高排列:

1. P1303A*B Problem(高精度乘法)入门
2. P1601A+B Problem(高精)入门
3. P2142高精度减法入门
4. P1932A+B A-B A*B A/B Problem入门
5. P1009阶乘之和(高精乘+高精加)普及-
6. P1255数楼梯(高精递推)普及-
7. P1604B进制星球(进制+高精)普及-
8. P1015回文数(高精+回文判断)普及-
9. P1017进制转换(高精除法取余)普及-
10. P1143进制转换(高精除法)普及-
11. P1010幂次方(高精+递归)普及
12. P106001背包(可能涉及大数)普及
13. P1982小朋友的数字(高精比较+加法)普及
14. P1149火柴棒等式(枚举+高精)普及
15. P1079Vigenère密码(字符串模拟)普及
16. P1067多项式输出(字符串处理)普及
17. P1517高精求小数(高精除法)提高
18. P1725琪露诺(DP+高精)提高
19. P1919A*B Problem 升级版(FFT)提高
20. P4777扩展中国定理(EXCRT 高精)提高

💡 刷题建议

入门阶段(第1~4题):先掌握高精加、减、乘、除的基本模板。

巩固阶段(第5~10题):练习阶乘、递推、进制转换等综合应用。

提高阶段(第11~16题):将高精度与 DP、枚举、字符串等结合。

挑战阶段(第17~20题):尝试 FFT 快速乘法、高精除法等进阶内容。