数论 | 中国剩余定理及其扩展
中国剩余定理(孙子定理):数论中的一个关于一元线性同余方程组的定理,说明了一元线性同余方程组有解的准则以及求解方法。
需要预备知识:扩展欧几里得算法
中国剩余定理
适用问题
有以下一元线性同余方程组:
整数
求解步骤
通解可以用如下方式构造得到:
- 设
是整数 的乘积,并设 ,即 是除了 以外的 个整数的乘积。 - 设
为 模 的数论倒数: - 方程组
的通解形式为: 在模 的意义下,方程组 只有一个解:
来源:Wikipedia (使用 CC BY-SA 3.0 协议)
其中,求数论倒数(即乘法逆元)的方式是扩展欧几里得算法,不可使用费马小定理。
简单证明
我们验证该解是否满足原一元线性同余方程组,首先考虑方程组中第一个方程:
首先,由于
同理可得,该解满足方程组中的所有方程,因此它是方程组的一个解。
扩展中国剩余定理
我们注意到,中国剩余定理的使用条件是整数
针对这种情况,我们使用扩展中国剩余定理求解。(该情况有可能无解)
适用问题
有以下一元线性同余方程组:
不保证整数
推导和求解步骤
联立其中两个方程
首先考虑
求解联立方程组
消去
该式对应的裴蜀等式为
通过扩展欧几里得算法,求得
若
将解回代
将解出的
我们可以发现,
将方程组合并为一个方程
我们上面的过程,即可看作将
合并最后的结果便是一个式子,我们假设这个式子为:
求解最终合并式
若我们要求得最小的正整数解,
模板
typedef long long ll;
ll exgcd(ll a, ll b, ll &x, ll &y)
{
if (!b)
{
x = 1;
y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
// 如果中途输出-1,-1则需要立即停止
// first-a second-mod
pair<ll, ll> excrt(pair<ll, ll> a, pair<ll, ll> b)
{
ll ya, yb;
ll d = exgcd(a.second, b.second, ya, yb);
if ((b.first - a.first) % d)
return {-1, -1};
ya *= (b.first - a.first) / d;
ll tmp = b.second / d;
ya = (ya % tmp + tmp) % tmp;
a.first += a.second * ya;
a.second = a.second / d * b.second;
return a;
}
典型例题
原题链接:https://www.acwing.com/problem/content/206/
注意:
- 为防止乘法溢出,建议先除后乘。
- C 语言负数取模会成负数,因此取模需要写成 (
这种形式。 - 程序实现时并没有使用上述过程中的那么多变量,同一个变量被重复利用。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 30;
int n;
LL exgcd(LL a, LL b, LL &x, LL &y)
{
if (!b)
{
x = 1;
y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main()
{
cin >> n;
LL a1, m1;
cin >> a1 >> m1;
for (int i = 2; i <= n; i++)
{
LL a2, m2, y1, y2;
cin >> a2 >> m2;
LL d = exgcd(a1, a2, y1, y2);
if ((m2 - m1) % d)
{
cout << -1 << endl;
return 0;
}
y1 *= (m2 - m1) / d;
LL tmp = a2 / d;
y1 = (y1 % tmp + tmp) % tmp;
m1 += a1 * y1;
a1 = abs(a1 / d * a2);
}
cout << (m1 % a1 + a1) % a1 << endl;
return 0;
}
本文采用 CC BY-SA 4.0 许可,本文 Markdown 源码:Haotian-BiJi