跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

数论之中国剩余定理:从孙子算经到算法竞赛实现

中国剩余定理用于求解模数两两互质的线性同余方程组。核心思想是构造法,通过局部解叠加得到全局解。实现需结合扩展欧几里得算法求逆元及快速乘防止溢出。原理、C++ 代码实现及经典例题,涵盖常见误区与避坑指南。

协议工匠发布于 2026/2/9更新于 2026/10/2497 浏览
数论之中国剩余定理:从孙子算经到算法竞赛实现

数论之中国剩余定理:从孙子算经到算法竞赛实现

一、问题溯源:从'物不知其数'到线性同余方程组

1.1 经典问题引入

《孙子算经》中有云:'今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?'翻译成数学语言,就是求解以下线性同余方程组: (此处为公式图示) 这个问题的最小正整数解是 23,而中国剩余定理正是为解决这类'多模数同余'问题而生。

1.2 线性同余方程组的定义

一般地,线性同余方程组的标准形式为: (此处为公式图示) 其中 m1, m2, ..., mn 是模数,r1, r2, ..., rn 是余数,我们的目标是找到满足所有方程的最小非负整数解 x。

1.3 CRT 的核心前提

中国剩余定理的适用条件是:所有模数 m1, m2, ..., mn 两两互质。这一前提是 CRT 构造解的基础,后续我们会看到,正是因为模数互质,才能保证逆元存在,从而顺利构造出满足所有方程的解。

二、CRT 的原理:构造法的精妙之处

CRT 的核心思想是'分而治之、逐步构造'——先为每个方程构造一个局部解,使得该解仅满足当前方程,而对其他方程的余数为 0;再将所有局部解相加,得到满足所有方程的全局解。

2.1 构造步骤拆解

设方程组有 n 个方程,模数两两互质,定义: (此处为公式图示) 则方程组的一个解为: (此处为公式图示)

2.2 构造逻辑验证

为什么这样的构造能满足所有方程?以第 i 个方程为例: 叠加后, (此处为公式图示) 恰好满足第 i 个方程。 对于 j=i, (此处为公式图示) 因此 (此处为公式图示) ; 对于 j≠i,Mj 包含 mi 这个因子(因为 M = m1 * ... * mn,而 mi 是 M 的因子且 mi 与 mj 互质),因此 (此处为公式图示) ;

三、核心工具:逆元与快速乘的实现

要实现 CRT,必须解决两个关键问题:乘法逆元求解和大整数溢出防护。

3.1 乘法逆元求解(扩展欧几里得算法)

由于 CRT 中模数两两互质,Mi 与 mi 互质,逆元一定存在。我们用扩展欧几里得算法求解逆元,该方法适用于任意互质的模数对,通用性强。

扩展欧几里得算法原理

扩展欧几里得算法不仅能计算 a 和 b 的最大公约数,还能找到整数 x 和 y,使得 ax+by=gcd(a,b)。当 a 与 b 互质时,gcd(a,b)=1,此时 x 就是 a 模 b 的逆元。

C++ 实现扩展欧几里得算法
#include <iostream>
using namespace std;
typedef long long LL;

// 扩展欧几里得算法:返回 gcd(a,b),通过引用返回 ax + by = gcd(a,b) 的特解 x,y
LL exgcd(LL a, LL b, LL& x, LL& y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    LL x1, y1, d = exgcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - a / b * y1;
     d;
}


{
    LL x, y;
    (a, m, x, y);
    
     (x % m + m) % m;
}

{
    LL a = , m = ;
    cout << a <<  << m <<  << (a, m) << endl; 
     ;
}
return
// 求 a 模 m 的逆元(a 与 m 互质)
LL get_inv(LL a, LL m)
exgcd
// 转为最小正整数逆元
return
int main()
35
3
"模"
"的逆元为:"
get_inv
// 输出 2
return
0

3.2 快速乘:大整数溢出防护

当模数较大时(如 1e5×1e5×1e5=1e15),直接乘法会超出 long long 范围导致溢出。快速乘(又称龟速乘)通过将乘法转化为加法,结合模运算,避免溢出。

快速乘原理

快速乘的核心是二进制分解:将 a×b 分解为 a×2^0+a×2^1+...+a×2^k,每次累加后取模,确保中间结果不溢出。

C++ 实现快速乘
// 快速乘:计算 (a*b) mod p,避免溢出
LL qmul(LL a, LL b, LL p) {
    LL ret = 0;
    while (b) {
        if (b & 1) {
            ret = (ret + a) % p;
        }
        a = (a + a) % p;
        b >>= 1;
    }
    return ret;
}

int main() {
    LL a = 1e18, b = 1e18, p = 1e9 + 7;
    cout << qmul(a % p, b % p, p) << endl; // 输出 (a*b) mod p 的结果
    return 0;
}

四、CRT 的完整 C++ 实现

结合逆元求解和快速乘,我们可以写出 CRT 的通用实现,适用于所有模数两两互质的线性同余方程组。

4.1 实现代码

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 15;

LL r[N], m[N]; // r[i] 为余数,m[i] 为模数
int n; // 方程个数

// 扩展欧几里得算法
LL exgcd(LL a, LL b, LL& x, LL& y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    LL x1, y1, d = exgcd(b, a % b, x1, y1);
    x = y1;
    y = x1 - a / b * y1;
    return d;
}

// 快速乘
LL qmul(LL a, LL b, LL p) {
    LL ret = 0;
    while (b) {
        if (b & 1) ret = (ret + a) % p;
        a = (a + a) % p;
        b >>= 1;
    }
    return ret;
}

// 中国剩余定理:返回最小非负解,无解返回 -1(模数互质时一定有解)
LL crt() {
    LL M = 1;
    for (int i = 0; i < n; ++i) {
        M *= m[i]; // 全局模数
    }
    LL ret = 0;
    for (int i = 0; i < n; ++i) {
        LL Mi = M / m[i];
        LL inv_Mi, _;
        // 求 Mi 模 m[i] 的逆元
        exgcd(Mi, m[i], inv_Mi, _);
        inv_Mi = (inv_Mi % m[i] + m[i]) % m[i];
        // 累加局部解:r[i] * Mi * inv_Mi,用快速乘避免溢出
        ret = (ret + qmul(qmul(r[i], Mi, M), inv_Mi, M)) % M;
    }
    // 确保返回最小非负解
    return (ret % M + M) % M;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    // 输入方程个数
    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> m[i] >> r[i];
    }
    LL ans = crt();
    cout << "最小非负解:" << ans << endl;
    return 0;
}

4.2 代码分析

  • 时间复杂度:O(nlogm),其中 n 是方程个数,logm 是扩展欧几里得算法和快速乘的时间复杂度;
  • 空间复杂度:O(n),仅需存储模数和余数数组;
  • 适用性:适用于所有模数两两互质的线性同余方程组,返回最小非负解;
  • 溢出防护:通过快速乘 qmul 确保大整数乘法不溢出,支持模数乘积达 1e18 级别。

五、实战例题 1:洛谷 P1495【模板】中国剩余定理 / 曹冲养猪

5.1 题目分析

题目描述:曹冲养猪,建 ai 个猪圈剩 bi 头猪(ai 两两互质),求最少养猪数量。 输入描述:第一行 n,接下来 n 行每行两个整数 ai、bi(ai 为模数,bi 为余数)。 输出描述:最小正整数解。 示例输入: 3 3 1 5 1 7 2 示例输出:16。

5.2 解题思路

直接套用 CRT 模板,将 ai 作为模数 mi,bi 作为余数 ri,调用 CRT 函数即可。

5.3 C++ 实现

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 15;
LL r[N], m[N];
int n;
LL exgcd(LL a, LL b, LL& x, LL& y) {
    if (b == 0) { x = 1, y = 0; return a; }
    LL x1, y1, d = exgcd(b, a % b, x1, y1);
    x = y1; y = x1 - a / b * y1; return d;
}
LL qmul(LL a, LL b, LL p) {
    LL ret = 0; while (b) { if (b & 1) ret = (ret + a) % p; a = (a + a) % p; b >>= 1; } return ret;
}
LL crt() {
    LL M = 1; for (int i = 0; i < n; ++i) M *= m[i];
    LL ret = 0;
    for (int i = 0; i < n; ++i) {
        LL Mi = M / m[i];
        LL inv_Mi, y;
        exgcd(Mi, m[i], inv_Mi, y);
        inv_Mi = (inv_Mi % m[i] + m[i]) % m[i];
        ret = (ret + qmul(qmul(r[i], Mi, M), inv_Mi, M)) % M;
    }
    return (ret + M) % M;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> n;
    for (int i = 0; i < n; ++i) { cin >> m[i] >> r[i]; }
    cout << crt() << endl;
    return 0;
}

5.4 代码验证

示例输入中,模数 m=[3,5,7],余数 r=[1,1,2]: 全局模数 M=105;局部解分别为:1×35×2=70,1×21×1=21,2×15×1=30;总和 70+21+30=121,121 mod 105=16,与示例输出一致。

六、实战例题 2:洛谷 P3868 [TJOI2009] 猜数字

6.1 题目分析

题目描述:给定两组数字 a1...ak 和 b1...bk(bi 两两互质),求最小的 n 使得 bi|(n−ai)(即 n≡ai(modbi))。 输入描述:第一行 k,第二行 a1...ak,第三行 b1...bk。 输出描述:最小正整数 n。 示例输入: 3 1 2 3 2 3 5 示例输出:23。

6.2 解题思路

方程转化:n≡ai(mod bi),直接对应 CRT 的标准形式;注意点:ai 可能为负数,需先将余数调整为非负(ri=(ai%bi+bi)%bi)。

6.3 C++ 实现

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 15;
LL r[N], m[N];
int n;
LL exgcd(LL a, LL b, LL& x, LL& y) {
    if (b == 0) { x = 1, y = 0; return a; }
    LL x1, y1, d = exgcd(b, a % b, x1, y1);
    x = y1; y = x1 - a / b * y1; return d;
}
LL qmul(LL a, LL b, LL p) {
    LL ret = 0; while (b) { if (b & 1) ret = (ret + a) % p; a = (a + a) % p; b >>= 1; } return ret;
}
LL crt() {
    LL M = 1; for (int i = 0; i < n; ++i) M *= m[i];
    LL ret = 0;
    for (int i = 0; i < n; ++i) {
        LL Mi = M / m[i];
        LL inv_Mi, y;
        exgcd(Mi, m[i], inv_Mi, y);
        inv_Mi = (inv_Mi % m[i] + m[i]) % m[i];
        // 累加时确保余数非负
        ret = (ret + qmul(qmul((r[i] % m[i] + m[i]) % m[i], Mi, M), inv_Mi, M)) % M;
    }
    return (ret + M) % M;
}
int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> n;
    for (int i = 0; i < n; ++i) { cin >> r[i]; }
    for (int i = 0; i < n; ++i) { cin >> m[i]; }
    cout << crt() << endl;
    return 0;
}

七、常见误区与避坑指南

7.1 模数不互质直接套用 CRT

  • 误区:忽略 CRT'模数两两互质'的前提,直接代入代码导致错误;
  • 避坑:先检查所有模数对的最大公约数,若存在非 1 的情况,改用 EXCRT;
  • 验证方法:对于模数 m1,m2,...,mn,需满足 gcd(mi,mj)=1(i≠j)。

7.2 大整数乘法溢出

  • 误区:直接使用 a * b % p 计算,当 a 和 b 均为 1e9 时溢出;
  • 避坑:统一使用快速乘 qmul 替代直接乘法,确保中间结果不溢出。

7.3 余数为负数未调整

  • 误区:当 ri 为负数时(如 x≡−1(mod5)),直接代入计算;
  • 避坑:将余数调整为非负:(ri % mi + mi) % mi。

7.4 逆元求解错误

  • 误区:使用费马小定理求逆元但未确认模数为质数;
  • 避坑:CRT 中模数不一定是质数,优先用扩展欧几里得算法求逆元(通用无限制)。

7.5 全局模数计算溢出

  • 误区:当 n 较大(如 n=10,每个模数 1e5),M=1e5^10 远超 long long 范围;
  • 避坑:此时需用 __int128 存储 M,或在快速乘中实时取模,避免全局模数溢出。

总结

中国剩余定理是解决线性同余方程组的经典算法,其核心是'构造法',通过局部解叠加得到全局解。

目录

  1. 数论之中国剩余定理:从孙子算经到算法竞赛实现
  2. 一、问题溯源:从“物不知其数”到线性同余方程组
  3. 1.1 经典问题引入
  4. 1.2 线性同余方程组的定义
  5. 1.3 CRT 的核心前提
  6. 二、CRT 的原理:构造法的精妙之处
  7. 2.1 构造步骤拆解
  8. 2.2 构造逻辑验证
  9. 三、核心工具:逆元与快速乘的实现
  10. 3.1 乘法逆元求解(扩展欧几里得算法)
  11. 扩展欧几里得算法原理
  12. C++ 实现扩展欧几里得算法
  13. 3.2 快速乘:大整数溢出防护
  14. 快速乘原理
  15. C++ 实现快速乘
  16. 四、CRT 的完整 C++ 实现
  17. 4.1 实现代码
  18. 4.2 代码分析
  19. 五、实战例题 1:洛谷 P1495【模板】中国剩余定理 / 曹冲养猪
  20. 5.1 题目分析
  21. 5.2 解题思路
  22. 5.3 C++ 实现
  23. 5.4 代码验证
  24. 六、实战例题 2:洛谷 P3868 [TJOI2009] 猜数字
  25. 6.1 题目分析
  26. 6.2 解题思路
  27. 6.3 C++ 实现
  28. 七、常见误区与避坑指南
  29. 7.1 模数不互质直接套用 CRT
  30. 7.2 大整数乘法溢出
  31. 7.3 余数为负数未调整
  32. 7.4 逆元求解错误
  33. 7.5 全局模数计算溢出
  34. 总结

更多推荐文章

查看全部
  • Stable Diffusion 3.5 创意工作流与云端 GPU 加速方案
  • Vue 基于 Python 的高校教材管理系统设计与实现
  • 网络安全入门指南:从零开始自学黑客技术
  • 大模型降低 AIGC 率指令策略与实战指南
  • 8 个提升 Python 数据分析效率的实用技巧
  • Kimi 2.5 开源模型全景部署手册
  • 使用 cpolar 实现 Proxmox VE 远程管理
  • C++ STL string 类从零实现详解
  • llama-cpp-python 完整安装与配置指南
  • go-zero 框架下随机图形验证码 API 接口实现
  • 智能梯控系统设计方案:跨品牌群控与 AI 调度架构解析
  • 大模型技术对汽车行业的影响与变革
  • 网络安全工程师职业定义、核心技能与认证体系详解
  • MySQL 数据库 Windows 免安装版配置与使用教程
  • Python3.8 环境下的 Stable Diffusion 轻量化部署
  • Net-NTLMv2 哈希的捕获与破解
  • LLM 常见归一化方法解析:LayerNorm、RMSNorm 与 DeepNorm
  • JavaAI 插件在 IntelliJ IDEA 中的安装与使用指南
  • AI 大模型应用开发指南:传统开发者转型路径
  • Nginx 高可用方案:基于 Keepalived 的双机热备实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online