信奥赛C++提高组csp-s之数论基础专题课:中国剩余定理2(编程案例实践1)

中国剩余定理(CRT)是数论中的一个重要定理,在信奥赛(NOI系列赛事)中属于必须掌握的模板级别知识。它主要用于求解一元线性同余方程组。
前面我们从数学原理 、手算例子 进行了详细讲解,接下来我们进行编程实战,先举第一个案例。
中国剩余定理(CRT)/ 曹冲养猪
题目描述
自从曹冲搞定了大象以后,曹操就开始捉摸让儿子干些事业,于是派他到中原养猪场养猪,可是曹冲满不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。举个例子,假如有 16 16 16 头母猪,如果建了 3 3 3 个猪圈,剩下 1 1 1 头猪就没有地方安家了。如果建造了 5 5 5 个猪圈,但是仍然有 1 1 1 头猪没有地方去,然后如果建造了 7 7 7 个猪圈,还有 2 2 2 头没有地方去。你作为曹总的私人秘书理所当然要将准确的猪数报给曹总,你该怎么办?
输入格式
第一行包含一个整数 n n n ------ 建立猪圈的次数,接下来 n n n 行,每行两个整数 a i , b i a_i, b_i ai,bi,表示建立了 a i a_i ai 个猪圈,有 b i b_i bi 头猪没有去处。你可以假定 a 1 ∼ a n a_1 \sim a_n a1∼an 互质。
输出格式
输出包含一个自然数,即为曹冲至少养母猪的数目。
输入输出样例 1
输入 1
3
3 1
5 1
7 2
输出 1
16
说明/提示
1 ≤ n ≤ 10 1 \leq n\le10 1≤n≤10, 0 ≤ b i < a i ≤ 100000 0 \leq b_i\lt a_i\le100000 0≤bi<ai≤100000, 1 ≤ ∏ a i ≤ 10 18 1 \leq \prod a_i \leq 10^{18} 1≤∏ai≤1018
思路分析
本题是**中国剩余定理(CRT)**的模板题。
题目给出 n n n 个同余方程:
x ≡ b i ( m o d a i ) ( i = 1 , ... , n ) x \equiv b_i \pmod{a_i} \quad (i=1,\dots,n) x≡bi(modai)(i=1,...,n)
其中 a i a_i ai 两两互质,要求最小的非负整数解 x x x。
中国剩余定理 :
设 M = ∏ i = 1 n a i M = \prod_{i=1}^{n} a_i M=∏i=1nai, M i = M / a i M_i = M / a_i Mi=M/ai, t i t_i ti 是 M i M_i Mi 在模 a i a_i ai 下的乘法逆元(即 M i ⋅ t i ≡ 1 ( m o d a i ) M_i \cdot t_i \equiv 1 \pmod{a_i} Mi⋅ti≡1(modai))。
则方程组的解为:
x ≡ ∑ i = 1 n b i ⋅ M i ⋅ t i ( m o d M ) x \equiv \sum_{i=1}^{n} b_i \cdot M_i \cdot t_i \pmod{M} x≡∑i=1nbi⋅Mi⋅ti(modM)
最小非负解即为 x m o d M x \bmod M xmodM。
注意点:
- 由于 ∏ a i ≤ 10 18 \prod a_i \le 10^{18} ∏ai≤1018, M M M 在
long long范围内,但计算 b i ⋅ M i ⋅ t i b_i \cdot M_i \cdot t_i bi⋅Mi⋅ti 时可能超过long long,需要使用__int128进行乘法以避免溢出。 - 逆元通过扩展欧几里得算法 求解,因为 M i M_i Mi 与 a i a_i ai 互质,逆元一定存在。
- 最后答案取模 M M M 后输出(保证非负)。
代码实现
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 扩展欧几里得:求 ax + by = gcd(a,b) 的一组解,返回 gcd
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b == 0) { x = 1; y = 0; return a; }
ll g = exgcd(b, a % b, y, x);
y -= a / b * x;
return g;
}
int main() {
int n; cin >> n;
ll a[15], b[15], M = 1; // a:模数, b:余数, M:总乘积
for (int i = 1; i <= n; ++i) {
cin >> a[i] >> b[i];
M *= a[i]; // 累乘得到 M,题目保证 M ≤ 1e18
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
ll Mi = M / a[i]; // Mi = M / a[i]
ll ti, y; // ti 是 Mi 模 a[i] 的逆元
exgcd(Mi, a[i], ti, y); // 解 Mi*ti + a[i]*y = 1,得到 ti
ti = (ti % a[i] + a[i]) % a[i]; // 保证 ti 为正
// 计算 b[i] * Mi % M * ti % M,用 __int128 防止溢出
ans = (ans + (__int128)b[i] * Mi % M * ti) % M;
}
cout << ans << endl;
return 0;
}
功能分析
- 输入处理 :读入 n n n 和每一对 ( a i , b i ) (a_i, b_i) (ai,bi),同时累乘得到 M M M。
- 扩展欧几里得函数 :
exgcd用于求解 M i ⋅ t i + a i ⋅ y = 1 M_i \cdot t_i + a_i \cdot y = 1 Mi⋅ti+ai⋅y=1,从而得到 t i t_i ti(模 a i a_i ai 下的逆元)。
因为 a i a_i ai 与 M i M_i Mi 互质,所以 gcd ( M i , a i ) = 1 \gcd(M_i, a_i) = 1 gcd(Mi,ai)=1,方程必有解,且解出的 t i t_i ti 即为逆元。 - 核心计算 :
- 对每个方程,计算 M i = M / a i M_i = M / a_i Mi=M/ai。
- 调用
exgcd求逆元 t i t_i ti,并调整为最小非负整数。 - 累加 b i ⋅ M i ⋅ t i b_i \cdot M_i \cdot t_i bi⋅Mi⋅ti 到答案,每一步乘法都使用
__int128暂存,最后对 M M M 取模。
- 输出:输出最终的非负整数解。
更多系列知识,请查看专栏:《信奥赛C++提高组csp-s知识详解及案例实践》:
https://blog.csdn.net/weixin_66461496/category_13113932.html
各种学习资料,助力大家一站式学习和提升!!!
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"########## 一站式掌握信奥赛知识! ##########";
cout<<"############# 冲刺信奥赛拿奖! #############";
cout<<"###### 课程购买后永久学习,不受限制! ######";
return 0;
}
1、csp信奥赛高频考点知识详解及案例实践:
CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转
CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转
信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html
2、csp信奥赛冲刺一等奖有效刷题题解:
CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转
信奥赛C++提高组csp-s初赛&复赛真题题解(持续更新)
https://blog.csdn.net/weixin_66461496/category_13125089.html
3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html
4、csp/信奥赛C++,完整信奥赛系列课程(永久学习):
https://edu.csdn.net/lecturer/7901 点击跳转
· 文末祝福 ·
cpp
#include<bits/stdc++.h>
using namespace std;
int main(){
cout<<"跟着王老师一起学习信奥赛C++";
cout<<" 成就更好的自己! ";
cout<<" csp信奥赛一等奖属于你! ";
return 0;
}