题解:AcWing 204 表达整数的奇怪方式
【题目来源】
AcWing:204 表达整数的奇怪方式 - AcWing题库
【题目描述】
给定 \(2n\) 个整数 \(a_1,a_2,\dots,a_n\) 和 \(m_1,m_2,\dots,m_n\),求一个最小的非负整数 \(x\),满足 \(\forall \in [1,n],x\equiv m_i(mod\ a_i)\)。
【输入】
第 \(1\) 行包含整数 \(n\)。
第 \(2\dots n+1\) 行:每 \(i+1\) 行包含两个整数 \(a_i\) 和 \(m_i\),数之间用空格隔开。
【输出】
输出最小非负整数,如果 \(x\) 不存在,则输出 \(-1\)。
【输入样例】
2
8 7
11 9
【输出样例】
31
【核心思想】
-
问题分析:给定 \(n\) 个同余方程 \(x \equiv m_i \pmod{a_i}\),求最小非负整数 \(x\) 满足所有方程,若不存在则输出 \(-1\)。这是一个经典的数论问题,核心在于利用中国剩余定理(CRT)将多个同余方程两两合并,逐步求解。
-
算法选择:
- 中国剩余定理(CRT):用于求解一组同余方程的公共解。当模数两两互质时,有唯一解模 \(\prod a_i\);当模数不互质时,需要判断方程是否相容(有解条件)
- 扩展欧几里得算法(exgcd):用于求解线性丢番图方程 \(a_1 k_1 + a_2 k_2 = \gcd(a_1, a_2)\) 的整数解,是合并两个同余方程的关键工具
- 两两合并策略:从第一个方程开始,逐个与后续方程合并,最终得到统一的同余方程
-
关键步骤:
- 读取输入:整数 \(n\)(方程数量),以及 \(n\) 对 \((a_i, m_i)\)
- 初始化:设当前合并结果为 \(x \equiv m_1 \pmod{a_1}\)
- 逐个合并(对于 \(i = 2\) 到 \(n\)):
- 当前方程:\(x \equiv m_1 \pmod{a_1}\)
- 下一个方程:\(x \equiv m_2 \pmod{a_2}\)
- 设 \(x = a_1 \cdot k_1 + m_1 = a_2 \cdot k_2 + m_2\),整理得:
- \(a_1 \cdot k_1 - a_2 \cdot k_2 = m_2 - m_1\)
- 使用 exgcd 求解 \(a_1 \cdot k_1 + a_2 \cdot k_2 = \gcd(a_1, a_2)\),得到一组特解 \((k_1, k_2)\)
- 判断有解性:若 \((m_2 - m_1) \bmod \gcd(a_1, a_2) \neq 0\),则方程组无解,输出 \(-1\)
- 调整特解:将 \(k_1\) 乘以 \((m_2 - m_1) / \gcd(a_1, a_2)\),得到原方程的特解
- 求最小非负解:令 \(t = a_2 / \gcd(a_1, a_2)\),将 \(k_1\) 调整到最小非负整数:
- \(k_1 = (k_1 \bmod t + t) \bmod t\)
- 更新合并结果:
- 新的余数:\(m_1 = a_1 \cdot k_1 + m_1\)
- 新的模数:\(a_1 = \text{lcm}(a_1, a_2) = a_1 / \gcd(a_1, a_2) \cdot a_2\)
- 将 \(m_1\) 对新的 \(a_1\) 取模,保持最小非负
- 输出答案:\((m_1 \bmod a_1 + a_1) \bmod a_1\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \cdot \log A_{\max})\),其中 \(A_{\max}\) 为 \(a_i\) 的最大值。每次合并需要一次 exgcd,时间为 \(O(\log(\min(a_1, a_2)))\)
- 空间复杂度:\(O(1)\),仅使用常数额外变量
-
中国剩余定理的核心思想:
- 同余方程合并:将 \(n\) 个同余方程两两合并,每次将两个方程 \(x \equiv m_1 \pmod{a_1}\) 和 \(x \equiv m_2 \pmod{a_2}\) 合并为一个新的方程 \(x \equiv m \pmod{\text{lcm}(a_1, a_2)}\)
- 有解条件:两个同余方程有公共解的充要条件是 \(m_1 \equiv m_2 \pmod{\gcd(a_1, a_2)}\),即 \((m_2 - m_1)\) 能被 \(\gcd(a_1, a_2)\) 整除。这是推广到非互质模数的关键条件
- 通解结构:若 \(x_0\) 是一组特解,则所有解为 \(x = x_0 + k \cdot \text{lcm}(a_1, a_2, \ldots, a_n)\),其中 \(k \in \mathbb{Z}\)
- exgcd 的桥梁作用:exgcd 不仅求出 \(\gcd(a_1, a_2)\),还构造出满足 \(a_1 k_1 + a_2 k_2 = \gcd(a_1, a_2)\) 的整数 \(k_1, k_2\),从而将同余问题转化为线性丢番图方程
- 与互质版 CRT 的区别:经典 CRT 要求模数两两互质,解唯一模 \(\prod a_i\);本题模数不一定互质,需要先判断相容性,解唯一模 \(\text{lcm}(a_1, \ldots, a_n)\)
- 适用于同余方程组求解、模线性方程、密码学中的模运算等问题
【解题思路】




【算法标签】
中国剩余定理
【代码详解】
#include <bits/stdc++.h>
using namespace std;
typedef long long LL; // 定义 LL 为 long long 类型
// 扩展欧几里得算法,用于求解 ax + by = gcd(a, b) 的一组整数解
LL exgcd(LL a, LL b, LL &x, LL &y)
{
if (b == 0) { // 如果 b 为 0,递归终止
x = 1, y = 0; // 解为 x = 1, y = 0
return a; // 返回 gcd(a, b)
}
LL r = exgcd(b, a % b, x, y); // 递归求解
LL x1 = x, y1 = y; // 保存递归结果
x = y1, y = x1 - a / b * y1; // 更新 x 和 y
return r; // 返回 gcd(a, b)
}
int main()
{
int n; // 定义整数 n,表示方程的数量
bool has_answer = true; // 标记是否有解
LL a1, m1; // a1 和 m1 表示第一个方程的系数和常数项
cin >> n; // 输入方程的数量
cin >> a1 >> m1; // 输入第一个方程的系数和常数项
// 循环处理剩余的 n - 1 个方程
for (int i = 0; i < n - 1; i++) {
LL a2, m2; // a2 和 m2 表示下一个方程的系数和常数项
cin >> a2 >> m2; // 输入下一个方程的系数和常数项
LL k1, k2; // k1 和 k2 用于存储扩展欧几里得算法的解
// 使用扩展欧几里得算法求解 a1 * k1 + a2 * k2 = gcd(a1, a2)
LL d = exgcd(a1, a2, k1, k2);
// 如果 (m1 - m2) 不能被 gcd(a1, a2) 整除,则无解
if ((m1 - m2) % d) {
has_answer = false; // 标记无解
break; // 跳出循环
}
// 调整 k1 和 k2 的值
k1 = k1 * (m2 - m1) / d;
k2 = k2 * (m2 - m1) / d;
LL t = a2 / d;
// 保证 k1 为最小非负整数解
k1 = (k1 + t) % t;
// 更新 m1 的值
m1 = a1 * k1 + m1;
// 更新 a1 的值
a1 = a1 / d * a2;
}
if (has_answer)
// 输出最小非负整数解
cout << (m1 + a1) % a1 << endl;
else
// 无解输出 -1
cout << -1 << endl;
return 0; // 程序结束
}
#include <bits/stdc++.h>
using namespace std;
#define int long long // 使用long long防止大数溢出
// 扩展欧几里得算法
int exgcd(int a, int b, int &x, int &y)
{
if (!b) // 基本情况:b = 0
{
x = 1; // 当b=0时,gcd(a,0)=a
y = 0; // 所以x=1, y=0满足 ax+0*y=a
return a; // 返回最大公约数
}
int x1, y1, d; // 局部变量存储递归结果
d = exgcd(b, a % b, x1, y1); // 递归调用
x = y1; // 递推公式:x = y1
y = x1 - a / b * y1; // 递推公式:y = x1 - (a/b)*y1
return d; // 返回最大公约数
}
signed main() // 因为用了#define int long long,所以用signed main
{
int n; // 方程个数
cin >> n;
bool has_answer = true; // 标记是否有解
int a1, m1; // 第一个方程的系数
cin >> a1 >> m1; // 输入第一个方程 x ≡ m1 (mod a1)
// 逐个合并方程
for (int i = 0; i < n - 1; i++) // 还需要合并n-1个方程
{
int a2, m2; // 下一个方程的系数
cin >> a2 >> m2; // 输入下一个方程 x ≡ m2 (mod a2)
int k1, k2; // 扩展欧几里得解
int d = exgcd(a1, a2, k1, k2); // 求解 a1*k1 + a2*k2 = d
// 判断是否有解
// 方程要相容,需要 (m2-m1) 能被 d 整除
if ((m2 - m1) % d) // 如果余数不为0
{
has_answer = false; // 无解
break;
}
// 从 a1*k1 + a2*k2 = d 得到 a1*k1 + a2*k2 = m2-m1
k1 *= (m2 - m1) / d; // 调整系数
// 将k1调整到最小非负解
int t = a2 / d; // 解的周期
k1 = (k1 % t + t) % t; // 确保k1是最小非负整数
// 更新合并后的方程参数
m1 = a1 * k1 + m1; // 新的余数
a1 = a1 / d * a2; // 新的模数(最小公倍数)
// 可选:确保m1是非负的
m1 = (m1 % a1 + a1) % a1;
}
if (has_answer)
{
// 输出最小非负整数解
int result = (m1 % a1 + a1) % a1;
cout << result << endl;
}
else
{
cout << -1 << endl; // 无解
}
return 0;
}
【运行结果】
2
8 7
11 9
31
浙公网安备 33010602011771号